mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-03-28 02:31:17 +01:00
# Description of Changes This pull request includes several changes to the codebase, focusing on enhancing OCR support, improving endpoint management, and adding new functionality for PDF compression. The most important changes are detailed below. ### Enhancements to OCR support: * `Dockerfile` and `Dockerfile.fat`: Added support for multiple new OCR languages including Chinese (Simplified), German, French, and Portuguese. (Our top 5 languages including English) [[1]](diffhunk://#diff-dd2c0eb6ea5cfc6c4bd4eac30934e2d5746747af48fef6da689e85b752f39557R69-R72) [[2]](diffhunk://#diff-571631582b988e88c52c86960cc083b0b8fa63cf88f056f26e9e684195221c27L78-R81) ### Improvements to endpoint management: * [`src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java`](diffhunk://#diff-750f31f6ecbd64b025567108a33775cad339e835a04360affff82a09410b697dR51-R66): Added a new method `isGroupEnabled` to check if a group of endpoints is enabled. * [`src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java`](diffhunk://#diff-750f31f6ecbd64b025567108a33775cad339e835a04360affff82a09410b697dL179-L193): Updated endpoint groups and removed redundant qpdf endpoints. [[1]](diffhunk://#diff-750f31f6ecbd64b025567108a33775cad339e835a04360affff82a09410b697dL179-L193) [[2]](diffhunk://#diff-750f31f6ecbd64b025567108a33775cad339e835a04360affff82a09410b697dL243-L244) * [`src/main/java/stirling/software/SPDF/config/EndpointInspector.java`](diffhunk://#diff-845de13e140bb1264014539714860f044405274ad2a9481f38befdd1c1333818R1-R291): Introduced a new `EndpointInspector` class to discover and validate GET endpoints dynamically. ### New functionality for PDF compression: * [`src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java`](diffhunk://#diff-c307589e9f958f2593c9567c5ad9d63cd03788aa4803b3017b1c13b0d0485805R10): Enhanced the `CompressController` to handle nested images within form XObjects, improving the accuracy of image compression in PDFs. Remove Compresses Dependency on QPDF [[1]](diffhunk://#diff-c307589e9f958f2593c9567c5ad9d63cd03788aa4803b3017b1c13b0d0485805R10) [[2]](diffhunk://#diff-c307589e9f958f2593c9567c5ad9d63cd03788aa4803b3017b1c13b0d0485805R28-R44) [[3]](diffhunk://#diff-c307589e9f958f2593c9567c5ad9d63cd03788aa4803b3017b1c13b0d0485805L49-R61) [[4]](diffhunk://#diff-c307589e9f958f2593c9567c5ad9d63cd03788aa4803b3017b1c13b0d0485805R77-R99) [[5]](diff hunk://#diff-c307589e9f958f2593c9567c5ad9d63cd03788aa4803b3017b1c13b0d0485805L92-R191) Closes #(issue_number) --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: a <a>
217 lines
7.5 KiB
Java
217 lines
7.5 KiB
Java
package stirling.software.SPDF.config;
|
|
|
|
import java.lang.reflect.Method;
|
|
import java.util.HashSet;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.TreeSet;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.context.ApplicationContext;
|
|
import org.springframework.context.ApplicationListener;
|
|
import org.springframework.context.event.ContextRefreshedEvent;
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.web.bind.annotation.RequestMethod;
|
|
import org.springframework.web.method.HandlerMethod;
|
|
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
|
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
|
|
|
@Component
|
|
public class EndpointInspector implements ApplicationListener<ContextRefreshedEvent> {
|
|
private static final Logger logger = LoggerFactory.getLogger(EndpointInspector.class);
|
|
|
|
private final ApplicationContext applicationContext;
|
|
private final Set<String> validGetEndpoints = new HashSet<>();
|
|
private boolean endpointsDiscovered = false;
|
|
|
|
@Autowired
|
|
public EndpointInspector(ApplicationContext applicationContext) {
|
|
this.applicationContext = applicationContext;
|
|
}
|
|
|
|
@Override
|
|
public void onApplicationEvent(ContextRefreshedEvent event) {
|
|
if (!endpointsDiscovered) {
|
|
discoverEndpoints();
|
|
endpointsDiscovered = true;
|
|
logger.info("Discovered {} valid GET endpoints", validGetEndpoints.size());
|
|
}
|
|
}
|
|
|
|
private void discoverEndpoints() {
|
|
try {
|
|
Map<String, RequestMappingHandlerMapping> mappings =
|
|
applicationContext.getBeansOfType(RequestMappingHandlerMapping.class);
|
|
|
|
for (Map.Entry<String, RequestMappingHandlerMapping> entry : mappings.entrySet()) {
|
|
RequestMappingHandlerMapping mapping = entry.getValue();
|
|
Map<RequestMappingInfo, HandlerMethod> handlerMethods = mapping.getHandlerMethods();
|
|
|
|
for (Map.Entry<RequestMappingInfo, HandlerMethod> handlerEntry :
|
|
handlerMethods.entrySet()) {
|
|
RequestMappingInfo mappingInfo = handlerEntry.getKey();
|
|
HandlerMethod handlerMethod = handlerEntry.getValue();
|
|
|
|
boolean isGetHandler = false;
|
|
try {
|
|
Set<RequestMethod> methods = mappingInfo.getMethodsCondition().getMethods();
|
|
isGetHandler = methods.isEmpty() || methods.contains(RequestMethod.GET);
|
|
} catch (Exception e) {
|
|
isGetHandler = true;
|
|
}
|
|
|
|
if (isGetHandler) {
|
|
Set<String> patterns = extractPatternsUsingDirectPaths(mappingInfo);
|
|
|
|
if (patterns.isEmpty()) {
|
|
patterns = extractPatternsFromString(mappingInfo);
|
|
}
|
|
|
|
validGetEndpoints.addAll(patterns);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (validGetEndpoints.isEmpty()) {
|
|
logger.warn("No endpoints discovered. Adding common endpoints as fallback.");
|
|
validGetEndpoints.add("/");
|
|
validGetEndpoints.add("/api/**");
|
|
validGetEndpoints.add("/**");
|
|
}
|
|
} catch (Exception e) {
|
|
logger.error("Error discovering endpoints", e);
|
|
}
|
|
}
|
|
|
|
private Set<String> extractPatternsUsingDirectPaths(RequestMappingInfo mappingInfo) {
|
|
Set<String> patterns = new HashSet<>();
|
|
|
|
try {
|
|
Method getDirectPathsMethod = mappingInfo.getClass().getMethod("getDirectPaths");
|
|
Object result = getDirectPathsMethod.invoke(mappingInfo);
|
|
if (result instanceof Set) {
|
|
@SuppressWarnings("unchecked")
|
|
Set<String> resultSet = (Set<String>) result;
|
|
patterns.addAll(resultSet);
|
|
}
|
|
} catch (Exception e) {
|
|
// Return empty set if method not found or fails
|
|
}
|
|
|
|
return patterns;
|
|
}
|
|
|
|
private Set<String> extractPatternsFromString(RequestMappingInfo mappingInfo) {
|
|
Set<String> patterns = new HashSet<>();
|
|
try {
|
|
String infoString = mappingInfo.toString();
|
|
if (infoString.contains("{")) {
|
|
String patternsSection =
|
|
infoString.substring(infoString.indexOf("{") + 1, infoString.indexOf("}"));
|
|
|
|
for (String pattern : patternsSection.split(",")) {
|
|
pattern = pattern.trim();
|
|
if (!pattern.isEmpty()) {
|
|
patterns.add(pattern);
|
|
}
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
// Return empty set if parsing fails
|
|
}
|
|
return patterns;
|
|
}
|
|
|
|
public boolean isValidGetEndpoint(String uri) {
|
|
if (!endpointsDiscovered) {
|
|
discoverEndpoints();
|
|
endpointsDiscovered = true;
|
|
}
|
|
|
|
if (validGetEndpoints.contains(uri)) {
|
|
return true;
|
|
}
|
|
|
|
if (matchesWildcardOrPathVariable(uri)) {
|
|
return true;
|
|
}
|
|
|
|
if (matchesPathSegments(uri)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private boolean matchesWildcardOrPathVariable(String uri) {
|
|
for (String pattern : validGetEndpoints) {
|
|
if (pattern.contains("*") || pattern.contains("{")) {
|
|
int wildcardIndex = pattern.indexOf('*');
|
|
int variableIndex = pattern.indexOf('{');
|
|
|
|
int cutoffIndex;
|
|
if (wildcardIndex < 0) {
|
|
cutoffIndex = variableIndex;
|
|
} else if (variableIndex < 0) {
|
|
cutoffIndex = wildcardIndex;
|
|
} else {
|
|
cutoffIndex = Math.min(wildcardIndex, variableIndex);
|
|
}
|
|
|
|
String staticPrefix = pattern.substring(0, cutoffIndex);
|
|
|
|
if (uri.startsWith(staticPrefix)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private boolean matchesPathSegments(String uri) {
|
|
for (String pattern : validGetEndpoints) {
|
|
if (!pattern.contains("*") && !pattern.contains("{")) {
|
|
String[] patternSegments = pattern.split("/");
|
|
String[] uriSegments = uri.split("/");
|
|
|
|
if (uriSegments.length < patternSegments.length) {
|
|
continue;
|
|
}
|
|
|
|
boolean match = true;
|
|
for (int i = 0; i < patternSegments.length; i++) {
|
|
if (!patternSegments[i].equals(uriSegments[i])) {
|
|
match = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (match) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public Set<String> getValidGetEndpoints() {
|
|
if (!endpointsDiscovered) {
|
|
discoverEndpoints();
|
|
endpointsDiscovered = true;
|
|
}
|
|
return new HashSet<>(validGetEndpoints);
|
|
}
|
|
|
|
private void logAllEndpoints() {
|
|
Set<String> sortedEndpoints = new TreeSet<>(validGetEndpoints);
|
|
|
|
logger.info("=== BEGIN: All discovered GET endpoints ===");
|
|
for (String endpoint : sortedEndpoints) {
|
|
logger.info("Endpoint: {}", endpoint);
|
|
}
|
|
logger.info("=== END: All discovered GET endpoints ===");
|
|
}
|
|
}
|