diff --git a/examples/src/main/java/org/apache/pdfbox/examples/LocalPdfServer.java b/examples/src/main/java/org/apache/pdfbox/examples/LocalPdfServer.java new file mode 100644 index 00000000000..8b47a2f0a52 --- /dev/null +++ b/examples/src/main/java/org/apache/pdfbox/examples/LocalPdfServer.java @@ -0,0 +1,140 @@ +package org.apache.pdfbox.examples; + +import com.sun.net.httpserver.HttpServer; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpExchange; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.font.PDType0Font; + +import java.io.*; +import java.net.InetSocketAddress; +import java.nio.file.Files; +import java.util.*; + +public class LocalPdfServer { + public static void main(String[] args) throws IOException { + int port = 8080; + HttpServer server = HttpServer.create(new InetSocketAddress(port), 0); + System.out.println("Server started at: http://localhost:" + port); + + server.createContext("/generate-pdf", new PdfGeneratorHandler()); + server.setExecutor(null); + server.start(); + } + + static class PdfGeneratorHandler implements HttpHandler { + @Override + public void handle(HttpExchange exchange) throws IOException { + if ("OPTIONS".equals(exchange.getRequestMethod())) { // Handle preflight request + exchange.getResponseHeaders().set("Access-Control-Allow-Origin", "*"); + exchange.getResponseHeaders().set("Access-Control-Allow-Methods", "POST, OPTIONS"); + exchange.getResponseHeaders().set("Access-Control-Allow-Headers", "Content-Type"); + exchange.sendResponseHeaders(204, -1); // No content for preflight + return; + } + + if (!"POST".equals(exchange.getRequestMethod())) { + exchange.sendResponseHeaders(405, 0); + exchange.close(); + return; + } + + // Enable CORS for normal requests + exchange.getResponseHeaders().set("Access-Control-Allow-Origin", "*"); + exchange.getResponseHeaders().set("Content-Type", "application/pdf"); + + // Read request body + InputStream inputStream = exchange.getRequestBody(); + String requestBody = new String(inputStream.readAllBytes()); + + // Extract text and font from request + String text = requestBody.replaceAll(".*\"text\":\"(.*?)\".*", "$1"); + String fontKey = requestBody.replaceAll(".*\"font\":\"(.*?)\".*", "$1"); + + if (text.isEmpty()) { + exchange.sendResponseHeaders(400, 0); + exchange.getResponseBody().close(); + return; + } + + // Get font path + Map fonts = getFontMap(); + String fontPath = fonts.getOrDefault(fontKey, fonts.get("kalimati")); // Default to Kalimati + + File pdfFile = new File("generated.pdf"); + generatePDF(text, pdfFile, fontPath); + + byte[] pdfData = Files.readAllBytes(pdfFile.toPath()); + exchange.sendResponseHeaders(200, pdfData.length); + exchange.getResponseBody().write(pdfData); + exchange.getResponseBody().close(); + } + } + + private static void generatePDF(String text, File pdfFile, String fontPath) throws IOException { + PDDocument document = new PDDocument(); + PDPage page = new PDPage(); + document.addPage(page); + + PDPageContentStream contentStream = new PDPageContentStream(document, page); + PDType0Font pdfFont = PDType0Font.load(document, new File(fontPath)); + + float startX = 50, startY = 700, fontSize = 14, leading = 1.5f * fontSize; + + contentStream.beginText(); + contentStream.setFont(pdfFont, fontSize); + contentStream.newLineAtOffset(startX, startY); + + List wrappedText = wrapText(text, pdfFont, fontSize, page.getMediaBox().getWidth() - 2 * startX); + for (String line : wrappedText) { + contentStream.showText(line); + contentStream.newLineAtOffset(0, -leading); + } + + contentStream.endText(); + contentStream.close(); + document.save(pdfFile); + document.close(); + } + + private static Map getFontMap() { + Map fontMap = new LinkedHashMap<>(); + fontMap.put("noto", "examples/src/main/resources/org/apache/pdfbox/resources/ttf/NotoSansDevanagariRegular.ttf"); + fontMap.put("noto_the_group", "examples/src/main/resources/org/apache/pdfbox/resources/ttf/NotoTheGroup.ttf"); + fontMap.put("kokila", "examples/src/main/resources/org/apache/pdfbox/resources/ttf/Kokila.ttf"); + fontMap.put("nirmala", "examples/src/main/resources/org/apache/pdfbox/resources/ttf/Nirmala.ttf"); + fontMap.put("nirmala_the_group", "examples/src/main/resources/org/apache/pdfbox/resources/ttf/NirmalaTheGroup.ttf"); + fontMap.put("mangal", "examples/src/main/resources/org/apache/pdfbox/resources/ttf/MangalRegular.ttf"); + fontMap.put("lohit", "examples/src/main/resources/org/apache/pdfbox/resources/ttf/LohitDevanagari.ttf"); + fontMap.put("tiro", "examples/src/main/resources/org/apache/pdfbox/resources/ttf/TiroDevanagariHindiRegular.ttf"); + fontMap.put("kalimati", "examples/src/main/resources/org/apache/pdfbox/resources/ttf/Kalimati.ttf"); + fontMap.put("kanjirowa", "examples/src/main/resources/org/apache/pdfbox/resources/ttf/Kanjirowa.ttf"); + return fontMap; + } + + private static List wrapText(String text, PDType0Font font, float fontSize, float maxWidth) throws IOException { + List lines = new ArrayList<>(); + String[] words = text.split(" "); + StringBuilder currentLine = new StringBuilder(); + + for (String word : words) { + String testLine = currentLine.length() == 0 ? word : currentLine + " " + word; + float textWidth = font.getStringWidth(testLine) / 1000 * fontSize; + + if (textWidth > maxWidth) { + lines.add(currentLine.toString()); + currentLine = new StringBuilder(word); + } else { + currentLine.append(currentLine.length() == 0 ? word : " " + word); + } + } + + if (currentLine.length() > 0) { + lines.add(currentLine.toString()); + } + + return lines; + } +} diff --git a/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/NepaliDevanagariPdfGeneration.java b/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/NepaliDevanagariPdfGeneration.java new file mode 100644 index 00000000000..fbbac39042e --- /dev/null +++ b/examples/src/main/java/org/apache/pdfbox/examples/pdmodel/NepaliDevanagariPdfGeneration.java @@ -0,0 +1,145 @@ +package org.apache.pdfbox.examples.pdmodel; + + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.font.PDType0Font; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +/* + * @author Amar Dura + */ +public class NepaliDevanagariPdfGeneration { + + public static void main(String[] args) { + PDDocument document; + PDPageContentStream contentStream; + String text = getStringText(); + // Load the font file + Map fonts = getFontMap(); + + // Define initial position + float startX = 50; + float startY = 700; + float fontSize = 12; + float leading = 1.5f * fontSize; + try{ + document = new PDDocument(); + PDPage page = new PDPage(); + document.addPage(page); + contentStream = new PDPageContentStream(document, page); + List fontNames = new ArrayList<>(fonts.keySet()); + for (int i = 0; i < fonts.size(); i++) { + String fontName = fontNames.get(i); + String fontPath = fonts.get(fontName); + + File fontFile = new File(fontPath); + PDType0Font pdfFont = PDType0Font.load(document, fontFile); + + // Begin the contentStream + contentStream.beginText(); + contentStream.newLineAtOffset(startX, startY); + contentStream.setFont(pdfFont, fontSize); + + // Show font name + contentStream.showText(fontName); + // Move down for spacing (e.g., 2x the leading) + contentStream.newLineAtOffset(0, -1 * leading); + + // Split and show the text + List wrappedText = wrapText(text, pdfFont, fontSize, page.getMediaBox().getWidth() - 2 * startX); + for (String line : wrappedText) { + contentStream.showText(line); + contentStream.newLineAtOffset(0, -leading); + } + + contentStream.endText(); + + // Update the startY position for the next font block + startY -= wrappedText.size() * leading + 1.5 * leading; // Extra gap between font blocks + } + + contentStream.close(); + document.save("examples/src/main/resources/org/apache/pdfbox/examples/nepali/nepali.pdf"); + System.out.println("PDF created successfully."); + document.close(); + + + } + catch (IOException e){ + System.out.println("PROBLEM: "+e.getMessage()); + } + } + + private static Map getFontMap() { + String noto = "examples/src/main/resources/org/apache/pdfbox/resources/ttf/NotoSansDevanagariRegular.ttf"; + String noto_the_group = "examples/src/main/resources/org/apache/pdfbox/resources/ttf/NotoTheGroup.ttf"; + String kokila = "examples/src/main/resources/org/apache/pdfbox/resources/ttf/Kokila.ttf"; + String nirmala = "examples/src/main/resources/org/apache/pdfbox/resources/ttf/Nirmala.ttf"; + String nirmala_the_group = "examples/src/main/resources/org/apache/pdfbox/resources/ttf/NirmalaTheGroup.ttf"; + String mangal = "examples/src/main/resources/org/apache/pdfbox/resources/ttf/MangalRegular.ttf"; + String lohit = "examples/src/main/resources/org/apache/pdfbox/resources/ttf/LohitDevanagari.ttf"; + String tiro = "examples/src/main/resources/org/apache/pdfbox/resources/ttf/TiroDevanagariHindiRegular.ttf"; + String kalimati ="examples/src/main/resources/org/apache/pdfbox/resources/ttf/Kalimati.ttf"; + String kanjirowa ="examples/src/main/resources/org/apache/pdfbox/resources/ttf/Kanjirowa.ttf"; + + + + Map fontMap = new LinkedHashMap<>(); +// fontMap.put("मङ्गल", mangal); +// fontMap.put("नोटो सान्स देवनागरी", noto); + fontMap.put("लोहित",lohit); + fontMap.put("कालिमाटी", kalimati); +// fontMap.put("निर्मला पुरानो", nirmala); +// fontMap.put("निर्मला नयाँ", nirmala_the_group); + + return fontMap; + } + private static List wrapText(String text, PDType0Font font, float fontSize, float maxWidth) throws IOException { + List lines = new ArrayList<>(); + String[] words = text.split(" "); + StringBuilder currentLine = new StringBuilder(); + + for (String word : words) { + String testLine = currentLine.length() == 0 ? word : currentLine + " " + word; + float textWidth = font.getStringWidth(testLine) / 1000 * fontSize; + + if (textWidth > maxWidth) { + lines.add(currentLine.toString()); + currentLine = new StringBuilder(word); + } else { + currentLine.append(currentLine.length() == 0 ? word : " " + word); + } + } + + if (currentLine.length() > 0) { + lines.add(currentLine.toString()); + } + + return lines; + } + + private static String getStringText(){ + + String loclText = "ख झ ५ ८ ९"; + String akhnText ="क्ष त्र ज्ञ"; + String rphfText = "र्य र्त्य र्त्स्य र्यि र्यी र्त्स्यि"; + String rkrfText = "क्र प्र श्र क्ष्र त्र ज्ञ्र"; + String blwfText = "ङ्र ट्र ठ्र ड्र ढ्र"; + String halfText = "क्य ख्य ग्य छ्य थ्य ष्ट न्थ्य क्र्क र्क्क"; + String cjctText = "ङ्क ङ्क्त ट्क ड्म द्ध द्म द्द द्द्र"; + String presText = "क्क क्त क्न ग्न च्च ष्ट्र ल्ल"; + String abvsText ="काँ किँ कीँ केँ कैँ कोँ कौँ र्काँ र्किँ र्कीँ र्केँ र्कैँ र्कोँ र्कौँ "; + String blwsText = "रु रू ट्रु ट्रू ङ्कु"; + String pstsText ="की खी गी झी"; + String halnText = "द् ट् ढ् ड्"; + + String textOnPdf = "वर्त्स्य टर्कि गर्छन् सङ्क्षिप्त निर् "; + + return textOnPdf ; + } +} \ No newline at end of file diff --git a/examples/src/main/java/org/apache/pdfbox/examples/web/index.html b/examples/src/main/java/org/apache/pdfbox/examples/web/index.html new file mode 100644 index 00000000000..e05873ad19f --- /dev/null +++ b/examples/src/main/java/org/apache/pdfbox/examples/web/index.html @@ -0,0 +1,191 @@ + + + + + + Nepali PDF Generator + + + + +
+

Generate Nepali PDF

+ + + + + + +
+ Generated PDF: nepali.pdf + 📥 +
+
+ + + + + \ No newline at end of file diff --git a/examples/src/main/resources/org/apache/pdfbox/examples/nepali/nepali.pdf b/examples/src/main/resources/org/apache/pdfbox/examples/nepali/nepali.pdf new file mode 100644 index 00000000000..052259f55e6 Binary files /dev/null and b/examples/src/main/resources/org/apache/pdfbox/examples/nepali/nepali.pdf differ diff --git a/examples/src/main/resources/org/apache/pdfbox/resources/otf/AdobeDevanagari-Regular.otf b/examples/src/main/resources/org/apache/pdfbox/resources/otf/AdobeDevanagari-Regular.otf new file mode 100644 index 00000000000..3fe52bbd75f Binary files /dev/null and b/examples/src/main/resources/org/apache/pdfbox/resources/otf/AdobeDevanagari-Regular.otf differ diff --git a/examples/src/main/resources/org/apache/pdfbox/resources/otf/KalimatiRegular.otf b/examples/src/main/resources/org/apache/pdfbox/resources/otf/KalimatiRegular.otf new file mode 100644 index 00000000000..ef44117eaa2 Binary files /dev/null and b/examples/src/main/resources/org/apache/pdfbox/resources/otf/KalimatiRegular.otf differ diff --git a/examples/src/main/resources/org/apache/pdfbox/resources/otf/MangalRegular.otf b/examples/src/main/resources/org/apache/pdfbox/resources/otf/MangalRegular.otf new file mode 100644 index 00000000000..aa345d15bfa Binary files /dev/null and b/examples/src/main/resources/org/apache/pdfbox/resources/otf/MangalRegular.otf differ diff --git a/examples/src/main/resources/org/apache/pdfbox/resources/ttf/Kalimati.ttf b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/Kalimati.ttf new file mode 100644 index 00000000000..ef44117eaa2 Binary files /dev/null and b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/Kalimati.ttf differ diff --git a/examples/src/main/resources/org/apache/pdfbox/resources/ttf/Kanjirowa.ttf b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/Kanjirowa.ttf new file mode 100644 index 00000000000..2d4a22f68b7 Binary files /dev/null and b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/Kanjirowa.ttf differ diff --git a/examples/src/main/resources/org/apache/pdfbox/resources/ttf/Kokila.ttf b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/Kokila.ttf new file mode 100644 index 00000000000..149863651c8 Binary files /dev/null and b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/Kokila.ttf differ diff --git a/examples/src/main/resources/org/apache/pdfbox/resources/ttf/LohitDevanagari.ttf b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/LohitDevanagari.ttf new file mode 100644 index 00000000000..0641c211ad1 Binary files /dev/null and b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/LohitDevanagari.ttf differ diff --git a/examples/src/main/resources/org/apache/pdfbox/resources/ttf/MangalRegular.ttf b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/MangalRegular.ttf new file mode 100644 index 00000000000..aa345d15bfa Binary files /dev/null and b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/MangalRegular.ttf differ diff --git a/examples/src/main/resources/org/apache/pdfbox/resources/ttf/Nirmala.ttf b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/Nirmala.ttf new file mode 100644 index 00000000000..6767efbe787 Binary files /dev/null and b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/Nirmala.ttf differ diff --git a/examples/src/main/resources/org/apache/pdfbox/resources/ttf/NirmalaTheGroup.ttf b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/NirmalaTheGroup.ttf new file mode 100644 index 00000000000..f59ee7e4072 Binary files /dev/null and b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/NirmalaTheGroup.ttf differ diff --git a/examples/src/main/resources/org/apache/pdfbox/resources/ttf/NotoSansDevanagariRegular.ttf b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/NotoSansDevanagariRegular.ttf new file mode 100644 index 00000000000..9d7da09594b Binary files /dev/null and b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/NotoSansDevanagariRegular.ttf differ diff --git a/examples/src/main/resources/org/apache/pdfbox/resources/ttf/NotoTheGroup.ttf b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/NotoTheGroup.ttf new file mode 100644 index 00000000000..9738bfbfd38 Binary files /dev/null and b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/NotoTheGroup.ttf differ diff --git a/examples/src/main/resources/org/apache/pdfbox/resources/ttf/TiroDevanagariHindiRegular.ttf b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/TiroDevanagariHindiRegular.ttf new file mode 100644 index 00000000000..31da494f053 Binary files /dev/null and b/examples/src/main/resources/org/apache/pdfbox/resources/ttf/TiroDevanagariHindiRegular.ttf differ diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/CmapSubtable.java b/fontbox/src/main/java/org/apache/fontbox/ttf/CmapSubtable.java index e1c8dc74ec2..9a893959ef3 100644 --- a/fontbox/src/main/java/org/apache/fontbox/ttf/CmapSubtable.java +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/CmapSubtable.java @@ -37,8 +37,8 @@ public class CmapSubtable implements CmapLookup { private static final Logger LOG = LogManager.getLogger(CmapSubtable.class); - private static final long LEAD_OFFSET = 0xD800l - (0x10000 >> 10); - private static final long SURROGATE_OFFSET = 0x10000l - (0xD800 << 10) - 0xDC00; + private static final long LEAD_OFFSET = 0xD800L - (0x10000 >> 10); + private static final long SURROGATE_OFFSET = 0x10000L - (0xD800 << 10) - 0xDC00; private int platformId; private int platformEncodingId; diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/CompoundCharacterTokenizer.java b/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/CompoundCharacterTokenizer.java index b930458fe4b..8a28a31122f 100644 --- a/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/CompoundCharacterTokenizer.java +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/CompoundCharacterTokenizer.java @@ -80,10 +80,17 @@ private void validateCompoundWords(Set compoundWords) * @return A list of tokens like "_66_", "_71_71_", "74_79_70_". The "_" is sometimes missing at * the beginning or end, this has to be cleaned by the caller. */ + + // *** this is the main syllable tokenizing happens + // *** but the syllable is based on the entries found in the gsub table rather than the actual syllable system of the language + // *** this is font dependent public List tokenize(String text) { List tokens = new ArrayList<>(); + // *** regexExpression: pattern we are searching for + // *** text: text where regexExpression is searched + // *** regexMatcher: result of the search for pattern in the text Matcher regexMatcher = regexExpression.matcher(text); int lastIndexOfPrevMatch = 0; @@ -91,6 +98,7 @@ public List tokenize(String text) while (regexMatcher.find(lastIndexOfPrevMatch)) // this is where the magic happens: // the regexp is used to find a matching pattern for substitution { + // starching search int beginIndexOfNextMatch = regexMatcher.start(); String prevToken = text.substring(lastIndexOfPrevMatch, beginIndexOfNextMatch); diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphArraySplitterRegexImpl.java b/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphArraySplitterRegexImpl.java index 50d897d690e..7f080afc539 100644 --- a/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphArraySplitterRegexImpl.java +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GlyphArraySplitterRegexImpl.java @@ -42,10 +42,14 @@ public GlyphArraySplitterRegexImpl(Set> matchers) @Override public List> split(List glyphIds) { + // *** original glyphs ids into string form _342_352_321_ String originalGlyphsAsText = convertGlyphIdsToString(glyphIds); + + // *** this is where breaking / tokenizing happens List tokens = compoundCharacterTokenizer.tokenize(originalGlyphsAsText); List> modifiedGlyphs = new ArrayList<>(tokens.size()); + // *** this is converting the List back into List> tokens.forEach(token -> modifiedGlyphs.add(convertGlyphIdsToList(token))); return modifiedGlyphs; } diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorkerFactory.java b/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorkerFactory.java index 2e65dcf26a1..37e174c5665 100644 --- a/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorkerFactory.java +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorkerFactory.java @@ -44,7 +44,8 @@ public GsubWorker getGsubWorker(CmapLookup cmapLookup, GsubData gsubData) case BENGALI: return new GsubWorkerForBengali(cmapLookup, gsubData); case DEVANAGARI: - return new GsubWorkerForDevanagari(cmapLookup, gsubData); +// return new GsubWorkerForDevanagari(cmapLookup, gsubData); + return new GsubWorkerForDevanagariNepali(cmapLookup, gsubData); case GUJARATI: return new GsubWorkerForGujarati(cmapLookup, gsubData); case LATIN: diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorkerForDevanagari.java b/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorkerForDevanagari.java index 3df61bbb5c4..265b11dfe7b 100644 --- a/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorkerForDevanagari.java +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorkerForDevanagari.java @@ -30,19 +30,19 @@ import org.apache.logging.log4j.Logger; /** - * + * * Devanagari-specific implementation of GSUB system - * + * * @author JAVAUSER * */ public class GsubWorkerForDevanagari implements GsubWorker { private static final Logger LOG = LogManager.getLogger(GsubWorkerForDevanagari.class); - + private static final String RKRF_FEATURE = "rkrf"; private static final String VATU_FEATURE = "vatu"; - + /** * This sequence is very important. This has been taken from https://docs.microsoft.com/en-us/typography/script-development/devanagari @@ -61,7 +61,7 @@ public class GsubWorkerForDevanagari implements GsubWorker private final CmapLookup cmapLookup; private final GsubData gsubData; - + private final List rephGlyphIds; private final List beforeRephGlyphIds; private final List beforeHalfGlyphIds; @@ -104,7 +104,7 @@ public List applyTransforms(List originalGlyphIds) } private List applyRKRFFeature(ScriptFeature rkrfGlyphsForSubstitution, - List originalGlyphIds) + List originalGlyphIds) { Set> rkrfGlyphIds = rkrfGlyphsForSubstitution.getAllGlyphIdsForSubstitution(); if (rkrfGlyphIds.isEmpty()) @@ -261,4 +261,4 @@ private Integer getGlyphId(char character) { return cmapLookup.getGlyphId(character); } -} +} \ No newline at end of file diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorkerForDevanagariNepali.java b/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorkerForDevanagariNepali.java new file mode 100644 index 00000000000..1f909584685 --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/gsub/GsubWorkerForDevanagariNepali.java @@ -0,0 +1,367 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.fontbox.ttf.gsub; + +import java.util.*; + +import org.apache.fontbox.ttf.CmapLookup; +import org.apache.fontbox.ttf.model.GsubData; +import org.apache.fontbox.ttf.model.ScriptFeature; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Devanagari-specific implementation of GSUB system + * + * @author JAVAUSER + * @author Harish + */ +public class GsubWorkerForDevanagariNepali implements GsubWorker { + private static final Logger LOG = LogManager.getLogger(GsubWorkerForDevanagariNepali.class); + + private static final String RKRF_FEATURE = "rkrf"; + private static final String VATU_FEATURE = "vatu"; + + /** + * This sequence is very important. This has been taken from https://docs.microsoft.com/en-us/typography/script-development/devanagari + */ + private static final List FEATURES_IN_ORDER = + Arrays.asList( + "locl", + "nukt", + "akhn", + "rphf", + RKRF_FEATURE, + "blwf", + "half", + VATU_FEATURE, + "cjct", + "pres", + "abvs", + "blws", + "psts", + "haln", + "calt"); + + // Reph glyphs + private static final char[] REPH_CHARS = {'र', '्'}; + // Glyphs to precede reph + // *** TODO + // *** This may need correction, other dependent vowels should be added + // *** [DONE] added other BEFORE_REPH_CHARS + private static final char[] BEFORE_REPH_CHARS = {'ा','ि' ,'ी', 'ो', 'ौ', 'े', 'ै'}; + + // Devanagari vowel sign I + private static final char BEFORE_HALF_CHAR = 'ि'; + + private final CmapLookup cmapLookup; + private final GsubData gsubData; + + private final List rephGlyphIds; + private final List beforeRephGlyphIds; + private final List beforeHalfGlyphIds; + private final List noHalfCharacterGlyphIds; + + + private static final List NO_HALF_CONSONANTS = Arrays.asList( +// 'ङ', +// 'ट', +// 'ठ', +// 'ड', +// 'ढ', +// 'द' + ); + + GsubWorkerForDevanagariNepali(CmapLookup cmapLookup, GsubData gsubData) { + this.cmapLookup = cmapLookup; + this.gsubData = gsubData; + noHalfCharacterGlyphIds = getNoHalfConsonants(); + beforeHalfGlyphIds = getBeforeHalfGlyphIds(); + rephGlyphIds = getRephGlyphIds(); + beforeRephGlyphIds = getbeforeRephGlyphIds(); + } + + @Override + public List applyTransforms(List originalGlyphIds) { + // *** reph position is adjusted + // *** TODO + // *** reph positioning is simply based on 1st find the र् sequence but it affects the formation of half form of rakaar + // *** so the reph feature should be applied for the र् sequence at the start of the syllable otherwise the र् will form rakaar with the preceeding half consonant + + List intermediateGlyphsFromGsub = adjustRephPosition(originalGlyphIds); + intermediateGlyphsFromGsub = repositionGlyphs(intermediateGlyphsFromGsub); + // *** ि position is adjusted + for (String feature : FEATURES_IN_ORDER) { + // *** all the features may not be supported by the particular script/font, the information is available in GSubData + if (!gsubData.isFeatureSupported(feature)) { + if (feature.equals(RKRF_FEATURE) && gsubData.isFeatureSupported(VATU_FEATURE)) { + // Create your own rkrf feature from vatu feature + intermediateGlyphsFromGsub = applyRKRFFeature( + gsubData.getFeature(VATU_FEATURE), + intermediateGlyphsFromGsub); + } + LOG.debug("the feature {} was not found", feature); + continue; + } + + LOG.debug("applying the feature {}", feature); + ScriptFeature scriptFeature = gsubData.getFeature(feature); + intermediateGlyphsFromGsub = applyGsubFeature(scriptFeature, + intermediateGlyphsFromGsub); + } + return Collections.unmodifiableList(intermediateGlyphsFromGsub); + } + + // *** applying rakaar + private List applyRKRFFeature(ScriptFeature rkrfGlyphsForSubstitution, + List originalGlyphIds) { + Set> rkrfGlyphIds = rkrfGlyphsForSubstitution.getAllGlyphIdsForSubstitution(); + if (rkrfGlyphIds.isEmpty()) { + // *** no substitution is available for rkrf feature + LOG.debug("Glyph substitution list for {} is empty.", rkrfGlyphsForSubstitution.getName()); + return originalGlyphIds; + } + // Replace this with better implementation to get second GlyphId from rkrfGlyphIds + int rkrfReplacement = 0; + for (List firstList : rkrfGlyphIds) + // *** TOD0 + // *** Look for the features in the rkrf table + { + if (firstList.size() > 1) { + rkrfReplacement = firstList.get(1); + break; + } + } + + if (rkrfReplacement == 0) { + LOG.debug("Cannot find rkrf candidate. The rkrfGlyphIds doesn't contain lists of two elements."); + return originalGlyphIds; + } + + List rkrfList = new ArrayList<>(originalGlyphIds); + for (int index = originalGlyphIds.size() - 1; index > 1; index--) { + int raGlyph = originalGlyphIds.get(index); + if (raGlyph == rephGlyphIds.get(0)) { + int viramaGlyph = originalGlyphIds.get(index - 1); + if (viramaGlyph == rephGlyphIds.get(1)) { + // *** found an ् + र form which takes the rkrf + // *** the replacement is available as script feature + rkrfList.set(index - 1, rkrfReplacement); + rkrfList.remove(index); + } + } + } + return rkrfList; + } + + // *** TODO + // *** This function requires improvement + // *** It works for र्यो but doesn't work for र्थ्यो or र्न्थ्यो + // *** DONE for र्थ्यो + // *** Resolve the issue of half form of rakaar forming reph forms: forming र्क्क instead क्र्क + + private List adjustRephPosition(List originalGlyphIds) { + List rephAdjustedList = new ArrayList<>(originalGlyphIds); + for (int index = 0; index < originalGlyphIds.size() - 2; index++) { + int raGlyph = originalGlyphIds.get(index); + int viramaGlyph = originalGlyphIds.get(index + 1); + // *** found the reph in originalGlyphIds jump to the next glyph if available + // *** TODO + // *** after finding the reph position check if it is at the starting of the syllable otherwise skip repositioning + if (raGlyph == rephGlyphIds.get(0) && viramaGlyph == rephGlyphIds.get(1)) { +// if(!(index>0 && originalGlyphIds.get(index-1)==rephGlyphIds.get(1))){ +// continue; +// } + +// if(index+2<=originalGlyphIds.size()){ + int nextIndex = index + 2; + + // *** for multiple half consonants after the reph + while ((nextIndex + 1) < originalGlyphIds.size() && originalGlyphIds.get(nextIndex + 1) == viramaGlyph) { + nextIndex = nextIndex + 2; + } + // *** remove the reph from the original position + for (int i = 0; i < 2; i++) { + rephAdjustedList.remove(index);// र + }// ् + // *** place the reph in the current found position + rephAdjustedList.add(nextIndex - 1, raGlyph); + rephAdjustedList.add(nextIndex, viramaGlyph); + + if (nextIndex + 1 < originalGlyphIds.size()) { + int matraGlyph = originalGlyphIds.get(nextIndex + 1); + if (beforeRephGlyphIds.contains(matraGlyph)) { + rephAdjustedList.set(nextIndex - 1, matraGlyph); + rephAdjustedList.set(nextIndex, raGlyph); + rephAdjustedList.set(nextIndex + 1, viramaGlyph); + } + } + } + } +// } + return rephAdjustedList; + } + + // *** ि as beforeHalfGlyph + // *** TODO + // *** does it handle the situation where there are multiple half consonants before a consonant followed by ि + // *** DONE : works perfectly for न्थ्यि but not for र्न्थ्यि + // *** TODO + // *** for ड्कि ङ्कि + private List repositionGlyphs(List originalGlyphIds) { + List repositionedGlyphIds = new ArrayList<>(originalGlyphIds); + int listSize = repositionedGlyphIds.size(); + int foundIndex = listSize - 1; + int nextIndex = listSize - 2; + while (nextIndex > -1) { + int glyph = repositionedGlyphIds.get(foundIndex); + int prevIndex = foundIndex + 1; + if (beforeHalfGlyphIds.contains(glyph)) { + // *** the ि is brought in front of the base character + repositionedGlyphIds.remove(foundIndex); + repositionedGlyphIds.add(nextIndex--, glyph); + } else if (rephGlyphIds.get(1).equals(glyph) && prevIndex < listSize) { + // *** if the current character is ् and it is not the last character + // *** we check if the character next to ् is ि + int prevGlyph = repositionedGlyphIds.get(prevIndex); + + // *** let's skip the swap for consonants that does not have half forms + if (beforeHalfGlyphIds.contains(prevGlyph)) { + + int nextGlyph = repositionedGlyphIds.get(nextIndex); + if (!noHalfCharacterGlyphIds.contains(nextGlyph)) { + repositionedGlyphIds.remove(prevIndex); + repositionedGlyphIds.add(nextIndex--, prevGlyph); + } else if(nextIndex>0 && Objects.equals(repositionedGlyphIds.get(nextIndex - 1), rephGlyphIds.get(1))){ + repositionedGlyphIds.remove(prevIndex); + repositionedGlyphIds.add(nextIndex--, prevGlyph); + } + } + } + foundIndex = nextIndex--; + } + return repositionedGlyphIds; + } + + // ** we need the gsub feature specific implementation so, the exceptional behaviors can be handled + private List applyGsubFeature(ScriptFeature scriptFeature, List originalGlyphs) { + // *** this is only the keyset for particular script feature in the substitution table + Set> allGlyphIdsForSubstitution = scriptFeature.getAllGlyphIdsForSubstitution(); + if (allGlyphIdsForSubstitution.isEmpty()) { + LOG.debug("getAllGlyphIdsForSubstitution() for {} is empty", scriptFeature.getName()); + return originalGlyphs; + } + + // *** here we prepare the regexExpression inside CompoundCharacterTokenizer where: Set> ---> Set ----> String or Regex Pattern + // *** this happens for each script feature + GlyphArraySplitter glyphArraySplitter = new GlyphArraySplitterRegexImpl( + allGlyphIdsForSubstitution); + + // *** this is the complex part where the pattern searching is happening + List> tokens = glyphArraySplitter.split(originalGlyphs); + + List gsubProcessedGlyphs = new ArrayList<>(tokens.size()); + +// tokens.forEach(chunk -> +// { +// // *** if there is substitution for the chunk: group of glyphs in input obtained after splitting +// // *** the tokens contains the chunks that match the patterns from the gsub table and also the ones that are not present in the gsub table +// if (scriptFeature.canReplaceGlyphs(chunk)) +// { +// // *** search in the map obtained from gsub tables +// // *** if it is replacable(i.e. found in the gsub table, then we can replace with the different glyph) +// List replacementForGlyphs = scriptFeature.getReplacementForGlyphs(chunk); +// +// // *** we add up all the replacement for the glyphs in the original sequence to get the final glyph sequence +// gsubProcessedGlyphs.addAll(replacementForGlyphs); +// } +// else +// { +// gsubProcessedGlyphs.addAll(chunk); +// } +// }); + + for (int chunkIndex = 0; chunkIndex < tokens.size(); chunkIndex++) { + List chunk = tokens.get(chunkIndex); + + boolean isHalfFeature = Objects.equals(scriptFeature.getName(), "half"); + boolean isHalnFeature = Objects.equals(scriptFeature.getName(), "haln"); + if (isHalfFeature || isHalnFeature) { +// *** check for last chunk: like छन् ---> [छ] [न‌ ्] + boolean isLastChunk = (chunkIndex == tokens.size() - 1); + if (!isLastChunk && scriptFeature.canReplaceGlyphs(chunk)) { + List replacementForGlyphs = scriptFeature.getReplacementForGlyphs(chunk); + gsubProcessedGlyphs.addAll(replacementForGlyphs); + } + else if(scriptFeature.canReplaceGlyphs(chunk)){ + gsubProcessedGlyphs.addAll(chunk); + } + else { + gsubProcessedGlyphs.addAll(chunk); + } + } else { + if (scriptFeature.canReplaceGlyphs(chunk)) { + // *** search in the map obtained from gsub tables + // *** if it is replacable(i.e. found in the gsub table, then we can replace with the different glyph) + List replacementForGlyphs = scriptFeature.getReplacementForGlyphs(chunk); + + // *** we add up all the replacement for the glyphs in the original sequence to get the final glyph sequence + gsubProcessedGlyphs.addAll(replacementForGlyphs); + } else { + gsubProcessedGlyphs.addAll(chunk); + } + } + } + LOG.debug("originalGlyphs: {}, gsubProcessedGlyphs: {}", originalGlyphs, gsubProcessedGlyphs); + return gsubProcessedGlyphs; + } + + + private List getBeforeHalfGlyphIds() { + return List.of(getGlyphId(BEFORE_HALF_CHAR)); + } + + private List getNoHalfConsonants() { + List glyphIds = new ArrayList<>(); + for (char character : NO_HALF_CONSONANTS) { + glyphIds.add(getGlyphId(character)); + } + return Collections.unmodifiableList(glyphIds); + } + + private List getRephGlyphIds() { + List result = new ArrayList<>(); + for (char character : REPH_CHARS) { + result.add(getGlyphId(character)); + } + return Collections.unmodifiableList(result); + } + + private List getbeforeRephGlyphIds() { + List glyphIds = new ArrayList<>(); + for (char character : BEFORE_REPH_CHARS) { + glyphIds.add(getGlyphId(character)); + } + return Collections.unmodifiableList(glyphIds); + } + + private Integer getGlyphId(char character) { + return cmapLookup.getGlyphId(character); + } +} diff --git a/fontbox/src/test/java/org/apache/fontbox/ttf/gsub/GsubWorkerForDevanagariNepaliTest.java b/fontbox/src/test/java/org/apache/fontbox/ttf/gsub/GsubWorkerForDevanagariNepaliTest.java new file mode 100644 index 00000000000..5dd6199e661 --- /dev/null +++ b/fontbox/src/test/java/org/apache/fontbox/ttf/gsub/GsubWorkerForDevanagariNepaliTest.java @@ -0,0 +1,829 @@ +package org.apache.fontbox.ttf.gsub; + +import org.apache.fontbox.ttf.CmapLookup; +import org.apache.fontbox.ttf.TTFParser; +import org.apache.fontbox.ttf.TrueTypeFont; +import org.apache.fontbox.ttf.model.GsubData; +import org.apache.fontbox.ttf.model.ScriptFeature; +import org.apache.pdfbox.io.RandomAccessReadBufferedFile; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +class GsubWorkerForDevanagariNepaliTest { + + private static final String LOHIT_DEVANAGARI_TTF = + "src/test/resources/ttf/Lohit-Devanagari.ttf"; + + private CmapLookup cmapLookup; + private GsubWorker gsubWorkerForDevanagariNepali; + private GsubData gsubData; + + @BeforeEach + public void init() throws IOException + { + try (TrueTypeFont ttf = new TTFParser().parse(new RandomAccessReadBufferedFile(LOHIT_DEVANAGARI_TTF))) + { + cmapLookup = ttf.getUnicodeCmapLookup(); + gsubData = ttf.getGsubData(); + gsubWorkerForDevanagariNepali = new GsubWorkerFactory().getGsubWorker(cmapLookup, ttf.getGsubData()); + } + } + +// @Test +// void testApplyTransforms_locl() +// { +// // given +// List glyphsAfterGsub = Arrays.asList(642); +// +// // when +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds("प्त")); +// System.out.println("result: " + result); +// +// // then +// assertEquals(glyphsAfterGsub, result); +// } +// +// @Test +// void testApplyTransforms_nukt() +// { +// // given +// List glyphsAfterGsub = Arrays.asList(400,396,393); +// +// // when +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds("य़ज़क़")); +// +// // then +// assertEquals(glyphsAfterGsub, result); +// } +// +// @Test +// void testApplyTransforms_akhn() +// { +// // given +// List glyphsAfterGsub = Arrays.asList(520,521); +// +// // when +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds("क्षज्ञ")); +// +// // then +// assertEquals(glyphsAfterGsub, result); +// } +// +// +// @Test +// void testApplyTransform_rephReposition() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { +// // get the private method by name +// Method privateMethod = gsubWorkerForDevanagariNepali.getClass().getDeclaredMethod("adjustRephPosition",List.class ); +// privateMethod.setAccessible(true); +// List result = (List) privateMethod.invoke(gsubWorkerForDevanagariNepali,Arrays.asList(353,382,342,382,352,380)); +// // then +// assertEquals(Arrays.asList(342,382,352,380,353,382), result); +// } +// +// @Test +// void test_ApplyTransforms_rphf() +// { +// // given +// List glyphsAfterGsub = Arrays.asList(538, 352, 673); +// +// // when +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds("र्थ्यो")); +// // र्थ्यो -> र ् थ ् य ो +// +// // then +// assertEquals(glyphsAfterGsub, result); +// } +// +// @Disabled +// @Test +// void testApplyTransforms_rkrf() +// { +// // given +// List glyphsAfterGsub = Arrays.asList(588,597,595,602); +// +// // when +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds("क्रब्रप्रह्र")); +// +// // then +// assertEquals(glyphsAfterGsub, result); +// } +// +// @Test +// void testApplyTransforms_blwf() +// { +// // given +// List glyphsAfterGsub = Arrays.asList(602,336,516); +// +// // when +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds("ह्रट्र")); +// +// // then +// assertEquals(glyphsAfterGsub, result); +// } +// +// @Test +// void testApplyTransforms_half() +// { +// // given +// List glyphsAfterGsub = Arrays.asList(558,557,546,537); +// +// // when +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds("ह्स्भ्त्")); +// +// // then +// assertEquals(glyphsAfterGsub, result); +// } +// +// @Test +// void testApplyTransforms_private_half_exception() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { +// Method privateMethod = gsubWorkerForDevanagariNepali.getClass().getDeclaredMethod("applyGsubFeature", ScriptFeature.class,List.class ); +// privateMethod.setAccessible(true); +// // given +// List glyphsAfterGsub = Arrays.asList(332,345,382);// छन् +// +// // when +// List result = (List) privateMethod.invoke(gsubWorkerForDevanagariNepali,gsubData.getFeature("half"),Arrays.asList(332,345,382)); +// +// // then +// assertEquals(glyphsAfterGsub, result); +// } +// +// +// @Test +// void testApplyTransforms_vatu() +// { +// // given +// List glyphsAfterGsub = Arrays.asList(517,593,601,665); +// +// // when +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds("श्रत्रस्रघ्र")); +// +// // then +// assertEquals(glyphsAfterGsub, result); +// } +// +// @Disabled +// @Test +// void testApplyTransforms_cjct() +// { +// // given +// List glyphsAfterGsub = Arrays.asList(638,688,636,640,639); +// +// // when +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds("द्मद्ध्र्यब्दद्वद्य")); +// +// // then +// assertEquals(glyphsAfterGsub, result); +// } +// +// @Test +// void testApplyTransforms_pres() +// { +// // given +// List glyphsAfterGsub = Arrays.asList(603,605,617,652); +// +// // when +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds("शृक्तज्जह्ण")); +// +// // then +// assertEquals(glyphsAfterGsub, result); +// } +// +// @Disabled +// @Test +// void testApplyTransforms_abvs() +// { +// // given +// List glyphsAfterGsub = Arrays.asList(353,512,353,675,353,673); +// +// // when +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds("र्रैंरौंर्रो")); +// +// // then +// assertEquals(glyphsAfterGsub, result); +// } +// +// @Disabled +// @Test +// void testApplyTransforms_blws() +// { +// // given +// List glyphsAfterGsub = Arrays.asList(660,663,336,584,336,583); +// +// // when +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds("दृहृट्रूट्रु")); +// +// // then +// assertEquals(glyphsAfterGsub, result); +// } +// +// @Disabled +// @Test +// void testApplyTransforms_psts() +// { +// // given +// List glyphsAfterGsub = Arrays.asList(326,704,326,582,661,662); +// +// // when +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds("किंर्कींरुरू")); +// +// // then +// assertEquals(glyphsAfterGsub, result); +// } +// +// @Test +// void testApplyTransforms_haln() +// { +// // given +// List glyphsAfterGsub = Arrays.asList(539); +// +// // when +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds("द्")); +// +// // then +// assertEquals(glyphsAfterGsub, result); +// } +// +// @Disabled +// void testApplyTransforms_calt() +// { +// // given +// List glyphsAfterGsub = Arrays.asList(); +// +// // when +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds("")); +// +// // then +// assertEquals(glyphsAfterGsub, result); +// } +// + private List getGlyphIds(String word) + { + List originalGlyphIds = new ArrayList<>(); + + for (char unicodeChar : word.toCharArray()) + { + int glyphId = cmapLookup.getGlyphId(unicodeChar); + assertTrue(glyphId > 0); + originalGlyphIds.add(glyphId); + } + + return originalGlyphIds; + } + + // *** Testing final + +// // *** filename: akhn.txt +// // *** feature_name: akhn +// @Test +// void testApplyTransformsForAkhn() throws IOException { +// InputStream inputStream = getClass().getClassLoader().getResourceAsStream("gsub.lohit_devanagari.dev2.nepali/akhn.txt"); +// assertNotNull(inputStream, "Test data file not found!"); +// +// int totalTests = 0; +// int passedTests = 0; +// +// try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { +// List lines = reader.lines().collect(Collectors.toList()); +// +// for (String line : lines) { +// if (line.trim().isEmpty()) continue; // Skip empty lines +// +// String[] parts = line.split("="); +// if (parts.length != 2) continue; // Skip malformed lines +// +// totalTests++; +// +// String inputText = parts[0].trim(); +// List expectedGlyphs = Arrays.stream(parts[1].trim().split(",")) +// .map(String::trim) +// .map(Integer::parseInt) +// .collect(Collectors.toList()); +// +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds(inputText)); +// +// try { +// assertEquals(expectedGlyphs, result, "Failed for input: " + inputText); +// passedTests++; +// } catch (AssertionError e) { +// System.err.println("Test failed for input: " + inputText); +// System.err.println("Input: " + getGlyphIds(inputText)); System.err.println("Expected: " + expectedGlyphs); +// System.err.println("Got: " + result); +// } +// } +// } +// +// double passPercentage = (totalTests == 0) ? 0 : ((double) passedTests / totalTests) * 100; +// System.out.println("Total Tests: " + totalTests); +// System.out.println("Passed Tests: " + passedTests); +// System.out.println("Pass Percentage: " + String.format("%.2f", passPercentage) + "%"); +// +// assertTrue(passPercentage > 80, "Pass percentage is below 80%"); +// } +// +// // *** filename: rphf.txt +// // *** feature_name: rphf +// @Test +// void testApplyTransformsForRphf() throws IOException { +// InputStream inputStream = getClass().getClassLoader().getResourceAsStream("gsub.lohit_devanagari.dev2.nepali/rphf.txt"); +// assertNotNull(inputStream, "Test data file not found!"); +// +// int totalTests = 0; +// int passedTests = 0; +// +// try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { +// List lines = reader.lines().collect(Collectors.toList()); +// +// for (String line : lines) { +// if (line.trim().isEmpty()) continue; // Skip empty lines +// +// String[] parts = line.split("="); +// if (parts.length != 2) continue; // Skip malformed lines +// +// totalTests++; +// +// String inputText = parts[0].trim(); +// List expectedGlyphs = Arrays.stream(parts[1].trim().split(",")) +// .map(String::trim) +// .map(Integer::parseInt) +// .collect(Collectors.toList()); +// +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds(inputText)); +// +// try { +// assertEquals(expectedGlyphs, result, "Failed for input: " + inputText); +// passedTests++; +// } catch (AssertionError e) { +// System.err.println("Test failed for input: " + inputText); +// System.err.println("Input: " + getGlyphIds(inputText)); System.err.println("Expected: " + expectedGlyphs); +// System.err.println("Got: " + result); +// } +// } +// } +// +// double passPercentage = (totalTests == 0) ? 0 : ((double) passedTests / totalTests) * 100; +// System.out.println("Total Tests: " + totalTests); +// System.out.println("Passed Tests: " + passedTests); +// System.out.println("Pass Percentage: " + String.format("%.2f", passPercentage) + "%"); +// +// assertTrue(passPercentage > 80, "Pass percentage is below 80%"); +// } +// +// // *** filename: rkrf.txt +// // *** feature_name: rkrf +// @Test +// void testApplyTransformsForRkrf() throws IOException { +// InputStream inputStream = getClass().getClassLoader().getResourceAsStream("gsub.lohit_devanagari.dev2.nepali/rkrf.txt"); +// assertNotNull(inputStream, "Test data file not found!"); +// +// int totalTests = 0; +// int passedTests = 0; +// +// try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { +// List lines = reader.lines().collect(Collectors.toList()); +// +// for (String line : lines) { +// if (line.trim().isEmpty()) continue; // Skip empty lines +// +// String[] parts = line.split("="); +// if (parts.length != 2) continue; // Skip malformed lines +// +// totalTests++; +// +// String inputText = parts[0].trim(); +// List expectedGlyphs = Arrays.stream(parts[1].trim().split(",")) +// .map(String::trim) +// .map(Integer::parseInt) +// .collect(Collectors.toList()); +// +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds(inputText)); +// +// try { +// assertEquals(expectedGlyphs, result, "Failed for input: " + inputText); +// passedTests++; +// } catch (AssertionError e) { +// System.err.println("Test failed for input: " + inputText); +// System.err.println("Input: " + getGlyphIds(inputText)); System.err.println("Expected: " + expectedGlyphs); +// System.err.println("Got: " + result); +// } +// } +// } +// +// double passPercentage = (totalTests == 0) ? 0 : ((double) passedTests / totalTests) * 100; +// System.out.println("Total Tests: " + totalTests); +// System.out.println("Passed Tests: " + passedTests); +// System.out.println("Pass Percentage: " + String.format("%.2f", passPercentage) + "%"); +// +// assertTrue(passPercentage > 80, "Pass percentage is below 80%"); +// } +// +// // *** filename: blwf.txt +// // *** feature_name: blwf +// @Test +// void testApplyTransformsForBlwf() throws IOException { +// InputStream inputStream = getClass().getClassLoader().getResourceAsStream("gsub.lohit_devanagari.dev2.nepali/blwf.txt"); +// assertNotNull(inputStream, "Test data file not found!"); +// +// int totalTests = 0; +// int passedTests = 0; +// +// try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { +// List lines = reader.lines().collect(Collectors.toList()); +// +// for (String line : lines) { +// if (line.trim().isEmpty()) continue; // Skip empty lines +// +// String[] parts = line.split("="); +// if (parts.length != 2) continue; // Skip malformed lines +// +// totalTests++; +// +// String inputText = parts[0].trim(); +// List expectedGlyphs = Arrays.stream(parts[1].trim().split(",")) +// .map(String::trim) +// .map(Integer::parseInt) +// .collect(Collectors.toList()); +// +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds(inputText)); +// +// try { +// assertEquals(expectedGlyphs, result, "Failed for input: " + inputText); +// passedTests++; +// } catch (AssertionError e) { +// System.err.println("Test failed for input: " + inputText); +// System.err.println("Input: " + getGlyphIds(inputText)); System.err.println("Expected: " + expectedGlyphs); +// System.err.println("Got: " + result); +// } +// } +// } +// +// double passPercentage = (totalTests == 0) ? 0 : ((double) passedTests / totalTests) * 100; +// System.out.println("Total Tests: " + totalTests); +// System.out.println("Passed Tests: " + passedTests); +// System.out.println("Pass Percentage: " + String.format("%.2f", passPercentage) + "%"); +// +// assertTrue(passPercentage > 80, "Pass percentage is below 80%"); +// } +// +// // *** filename: half.txt +// // *** feature_name: half +// @Test +// void testApplyTransformsForHalf() throws IOException { +// InputStream inputStream = getClass().getClassLoader().getResourceAsStream("gsub.lohit_devanagari.dev2.nepali/half.txt"); +// assertNotNull(inputStream, "Test data file not found!"); +// +// int totalTests = 0; +// int passedTests = 0; +// +// try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { +// List lines = reader.lines().collect(Collectors.toList()); +// +// for (String line : lines) { +// if (line.trim().isEmpty()) continue; // Skip empty lines +// +// String[] parts = line.split("="); +// if (parts.length != 2) continue; // Skip malformed lines +// +// totalTests++; +// +// String inputText = parts[0].trim(); +// List expectedGlyphs = Arrays.stream(parts[1].trim().split(",")) +// .map(String::trim) +// .map(Integer::parseInt) +// .collect(Collectors.toList()); +// +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds(inputText)); +// +// try { +// assertEquals(expectedGlyphs, result, "Failed for input: " + inputText); +// passedTests++; +// } catch (AssertionError e) { +// System.err.println("Test failed for input: " + inputText); +// System.err.println("Input: " + getGlyphIds(inputText)); System.err.println("Expected: " + expectedGlyphs); +// System.err.println("Got: " + result); +// } +// } +// } +// +// double passPercentage = (totalTests == 0) ? 0 : ((double) passedTests / totalTests) * 100; +// System.out.println("Total Tests: " + totalTests); +// System.out.println("Passed Tests: " + passedTests); +// System.out.println("Pass Percentage: " + String.format("%.2f", passPercentage) + "%"); +// +// assertTrue(passPercentage > 80, "Pass percentage is below 80%"); +// } +// +// // *** filename: cjct.txt +// // *** feature_name: cjct +// @Test +// void testApplyTransformsForCjct() throws IOException { +// InputStream inputStream = getClass().getClassLoader().getResourceAsStream("gsub.lohit_devanagari.dev2.nepali/cjct.txt"); +// assertNotNull(inputStream, "Test data file not found!"); +// +// int totalTests = 0; +// int passedTests = 0; +// +// try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { +// List lines = reader.lines().collect(Collectors.toList()); +// +// for (String line : lines) { +// if (line.trim().isEmpty()) continue; // Skip empty lines +// +// String[] parts = line.split("="); +// if (parts.length != 2) continue; // Skip malformed lines +// +// totalTests++; +// +// String inputText = parts[0].trim(); +// List expectedGlyphs = Arrays.stream(parts[1].trim().split(",")) +// .map(String::trim) +// .map(Integer::parseInt) +// .collect(Collectors.toList()); +// +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds(inputText)); +// +// try { +// assertEquals(expectedGlyphs, result, "Failed for input: " + inputText); +// passedTests++; +// } catch (AssertionError e) { +// System.err.println("Test failed for input: " + inputText); +// System.err.println("Input: " + getGlyphIds(inputText)); System.err.println("Expected: " + expectedGlyphs); +// System.err.println("Got: " + result); +// } +// } +// } +// +// double passPercentage = (totalTests == 0) ? 0 : ((double) passedTests / totalTests) * 100; +// System.out.println("Total Tests: " + totalTests); +// System.out.println("Passed Tests: " + passedTests); +// System.out.println("Pass Percentage: " + String.format("%.2f", passPercentage) + "%"); +// +// assertTrue(passPercentage > 80, "Pass percentage is below 80%"); +// } +// +// // *** filename: pres.txt +// // *** feature_name: pres +// @Test +// void testApplyTransformsForPres() throws IOException { +// InputStream inputStream = getClass().getClassLoader().getResourceAsStream("gsub.lohit_devanagari.dev2.nepali/pres.txt"); +// assertNotNull(inputStream, "Test data file not found!"); +// +// int totalTests = 0; +// int passedTests = 0; +// +// try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { +// List lines = reader.lines().collect(Collectors.toList()); +// +// for (String line : lines) { +// if (line.trim().isEmpty()) continue; // Skip empty lines +// +// String[] parts = line.split("="); +// if (parts.length != 2) continue; // Skip malformed lines +// +// totalTests++; +// +// String inputText = parts[0].trim(); +// List expectedGlyphs = Arrays.stream(parts[1].trim().split(",")) +// .map(String::trim) +// .map(Integer::parseInt) +// .collect(Collectors.toList()); +// +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds(inputText)); +// +// try { +// assertEquals(expectedGlyphs, result, "Failed for input: " + inputText); +// passedTests++; +// } catch (AssertionError e) { +// System.err.println("Test failed for input: " + inputText); +// System.err.println("Input: " + getGlyphIds(inputText)); System.err.println("Expected: " + expectedGlyphs); +// System.err.println("Got: " + result); +// } +// } +// } +// +// double passPercentage = (totalTests == 0) ? 0 : ((double) passedTests / totalTests) * 100; +// System.out.println("Total Tests: " + totalTests); +// System.out.println("Passed Tests: " + passedTests); +// System.out.println("Pass Percentage: " + String.format("%.2f", passPercentage) + "%"); +// +// assertTrue(passPercentage > 80, "Pass percentage is below 80%"); +// } +// +// // *** filename: abvs.txt +// // *** feature_name: abvs +// @Test +// void testApplyTransformsForAbvs() throws IOException { +// InputStream inputStream = getClass().getClassLoader().getResourceAsStream("gsub.lohit_devanagari.dev2.nepali/abvs.txt"); +// assertNotNull(inputStream, "Test data file not found!"); +// +// int totalTests = 0; +// int passedTests = 0; +// +// try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { +// List lines = reader.lines().collect(Collectors.toList()); +// +// for (String line : lines) { +// if (line.trim().isEmpty()) continue; // Skip empty lines +// +// String[] parts = line.split("="); +// if (parts.length != 2) continue; // Skip malformed lines +// +// totalTests++; +// +// String inputText = parts[0].trim(); +// List expectedGlyphs = Arrays.stream(parts[1].trim().split(",")) +// .map(String::trim) +// .map(Integer::parseInt) +// .collect(Collectors.toList()); +// +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds(inputText)); +// +// try { +// assertEquals(expectedGlyphs, result, "Failed for input: " + inputText); +// passedTests++; +// } catch (AssertionError e) { +// System.err.println("Test failed for input: " + inputText); +// System.err.println("Input: " + getGlyphIds(inputText)); System.err.println("Expected: " + expectedGlyphs); +// System.err.println("Got: " + result); +// } +// } +// } +// +// double passPercentage = (totalTests == 0) ? 0 : ((double) passedTests / totalTests) * 100; +// System.out.println("Total Tests: " + totalTests); +// System.out.println("Passed Tests: " + passedTests); +// System.out.println("Pass Percentage: " + String.format("%.2f", passPercentage) + "%"); +// +// assertTrue(passPercentage > 80, "Pass percentage is below 80%"); +// } +// +// // *** filename: blws.txt +// // *** feature_name: blws +// @Test +// void testApplyTransformsForBlws() throws IOException { +// InputStream inputStream = getClass().getClassLoader().getResourceAsStream("gsub.lohit_devanagari.dev2.nepali/blws.txt"); +// assertNotNull(inputStream, "Test data file not found!"); +// +// int totalTests = 0; +// int passedTests = 0; +// +// try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { +// List lines = reader.lines().collect(Collectors.toList()); +// +// for (String line : lines) { +// if (line.trim().isEmpty()) continue; // Skip empty lines +// +// String[] parts = line.split("="); +// if (parts.length != 2) continue; // Skip malformed lines +// +// totalTests++; +// +// String inputText = parts[0].trim(); +// List expectedGlyphs = Arrays.stream(parts[1].trim().split(",")) +// .map(String::trim) +// .map(Integer::parseInt) +// .collect(Collectors.toList()); +// +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds(inputText)); +// +// try { +// assertEquals(expectedGlyphs, result, "Failed for input: " + inputText); +// passedTests++; +// } catch (AssertionError e) { +// System.err.println("Test failed for input: " + inputText); +// System.err.println("Input: " + getGlyphIds(inputText)); System.err.println("Expected: " + expectedGlyphs); +// System.err.println("Got: " + result); +// } +// } +// } +// +// double passPercentage = (totalTests == 0) ? 0 : ((double) passedTests / totalTests) * 100; +// System.out.println("Total Tests: " + totalTests); +// System.out.println("Passed Tests: " + passedTests); +// System.out.println("Pass Percentage: " + String.format("%.2f", passPercentage) + "%"); +// +// assertTrue(passPercentage > 80, "Pass percentage is below 80%"); +// } +// +// +// // *** filename: haln.txt +// // *** feature_name: haln +// @Test +// void testApplyTransformsForHaln() throws IOException { +// InputStream inputStream = getClass().getClassLoader().getResourceAsStream("gsub.lohit_devanagari.dev2.nepali/haln.txt"); +// assertNotNull(inputStream, "Test data file not found!"); +// +// int totalTests = 0; +// int passedTests = 0; +// +// try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { +// List lines = reader.lines().collect(Collectors.toList()); +// +// for (String line : lines) { +// if (line.trim().isEmpty()) continue; // Skip empty lines +// +// String[] parts = line.split("="); +// if (parts.length != 2) continue; // Skip malformed lines +// +// totalTests++; +// +// String inputText = parts[0].trim(); +// List expectedGlyphs = Arrays.stream(parts[1].trim().split(",")) +// .map(String::trim) +// .map(Integer::parseInt) +// .collect(Collectors.toList()); +// +// List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds(inputText)); +// +// try { +// assertEquals(expectedGlyphs, result, "Failed for input: " + inputText); +// passedTests++; +// } catch (AssertionError e) { +// System.err.println("Test failed for input: " + inputText); +// System.err.println("Input: " + getGlyphIds(inputText)); System.err.println("Expected: " + expectedGlyphs); +// System.err.println("Got: " + result); +// } +// } +// } +// +// double passPercentage = (totalTests == 0) ? 0 : ((double) passedTests / totalTests) * 100; +// System.out.println("Total Tests: " + totalTests); +// System.out.println("Passed Tests: " + passedTests); +// System.out.println("Pass Percentage: " + String.format("%.2f", passPercentage) + "%"); +// +// assertTrue(passPercentage > 80, "Pass percentage is below 80%"); +// } + + @ParameterizedTest + @CsvSource({ + "akhn, gsub.lohit_devanagari.dev2.nepali/akhn.txt", + "rphf, gsub.lohit_devanagari.dev2.nepali/rphf.txt", + "rkrf, gsub.lohit_devanagari.dev2.nepali/rkrf.txt", + "abvs, gsub.lohit_devanagari.dev2.nepali/abvs.txt", + "blwf, gsub.lohit_devanagari.dev2.nepali/blwf.txt", + "blws, gsub.lohit_devanagari.dev2.nepali/blws.txt", + "cjct, gsub.lohit_devanagari.dev2.nepali/cjct.txt", + "half, gsub.lohit_devanagari.dev2.nepali/half.txt", + "haln, gsub.lohit_devanagari.dev2.nepali/haln.txt", + "pres, gsub.lohit_devanagari.dev2.nepali/pres.txt", + // Add other features as needed + }) + void testApplyTransforms(String featureName, String filePath) throws IOException { + InputStream inputStream = getClass().getClassLoader().getResourceAsStream(filePath); + assertNotNull(inputStream, "Test data file for " + featureName + " not found!"); + + int totalTests = 0, passedTests = 0; + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + for (String line; (line = reader.readLine()) != null; ) { + if (line.trim().isEmpty() || !line.contains("=")) continue; + + totalTests++; + String[] parts = line.split("="); + String inputText = parts[0].trim(); + List expectedGlyphs = Arrays.stream(parts[1].trim().split(",")) + .map(String::trim) + .map(Integer::parseInt) + .collect(Collectors.toList()); + + List result = gsubWorkerForDevanagariNepali.applyTransforms(getGlyphIds(inputText)); + + if (expectedGlyphs.equals(result)) { + passedTests++; + } else { + System.err.printf("Feature: %s | Failed for input: %s%nExpected: %s%nGot: %s%n", + featureName, inputText, expectedGlyphs, result); + } + } + } + + double passPercentage = (totalTests == 0) ? 0 : ((double) passedTests / totalTests) * 100; + System.out.printf("Feature: %s | Total Tests: %d | Passed: %d | Pass %%: %.2f%%%n", + featureName, totalTests, passedTests, passPercentage); + + assertTrue(passPercentage > 90, "Pass percentage is below 80% for " + featureName); + } + + + +} + diff --git a/fontbox/src/test/java/org/apache/fontbox/ttf/gsub/GsubWorkerForDevanagariTest.java b/fontbox/src/test/java/org/apache/fontbox/ttf/gsub/GsubWorkerForDevanagariTest.java index b729f8bcb4f..dd8d76ec090 100644 --- a/fontbox/src/test/java/org/apache/fontbox/ttf/gsub/GsubWorkerForDevanagariTest.java +++ b/fontbox/src/test/java/org/apache/fontbox/ttf/gsub/GsubWorkerForDevanagariTest.java @@ -21,6 +21,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -28,6 +30,8 @@ import org.apache.fontbox.ttf.CmapLookup; import org.apache.fontbox.ttf.TTFParser; import org.apache.fontbox.ttf.TrueTypeFont; +import org.apache.fontbox.ttf.model.GsubData; +import org.apache.fontbox.ttf.model.ScriptFeature; import org.apache.pdfbox.io.RandomAccessReadBufferedFile; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -50,6 +54,7 @@ class GsubWorkerForDevanagariTest private CmapLookup cmapLookup; private GsubWorker gsubWorkerForDevanagari; + private GsubData gsubData; @BeforeEach void init() throws IOException @@ -57,6 +62,7 @@ void init() throws IOException try (TrueTypeFont ttf = new TTFParser().parse(new RandomAccessReadBufferedFile(LOHIT_DEVANAGARI_TTF))) { cmapLookup = ttf.getUnicodeCmapLookup(); + gsubData = ttf.getGsubData(); gsubWorkerForDevanagari = new GsubWorkerFactory().getGsubWorker(cmapLookup, ttf.getGsubData()); } } @@ -100,14 +106,26 @@ void testApplyTransforms_akhn() assertEquals(glyphsAfterGsub, result); } + + @Test + void testApplyTransform_rephReposition() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + // get the private method by name + Method privateMethod = gsubWorkerForDevanagari.getClass().getDeclaredMethod("adjustRephPosition",List.class ); + privateMethod.setAccessible(true); + List result = (List) privateMethod.invoke(gsubWorkerForDevanagari,Arrays.asList(353,382,342,382,352,380)); + // then + assertEquals(Arrays.asList(342,382,352,380,353,382), result); + } + @Test - void testApplyTransforms_rphf() + void test_ApplyTransforms_rphf() { // given - List glyphsAfterGsub = Arrays.asList(513); + List glyphsAfterGsub = Arrays.asList(538, 352, 673); // when - List result = gsubWorkerForDevanagari.applyTransforms(getGlyphIds("र्")); + List result = gsubWorkerForDevanagari.applyTransforms(getGlyphIds("र्थ्यो")); + // र्थ्यो -> र ् थ ् य ो // then assertEquals(glyphsAfterGsub, result); @@ -153,6 +171,21 @@ void testApplyTransforms_half() assertEquals(glyphsAfterGsub, result); } + @Test + void testApplyTransforms_private_half_exception() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + Method privateMethod = gsubWorkerForDevanagari.getClass().getDeclaredMethod("applyGsubFeature", ScriptFeature.class,List.class ); + privateMethod.setAccessible(true); + // given + List glyphsAfterGsub = Arrays.asList(332,345,382);// छन् + + // when + List result = (List) privateMethod.invoke(gsubWorkerForDevanagari,gsubData.getFeature("half"),Arrays.asList(332,345,382)); + + // then + assertEquals(glyphsAfterGsub, result); + } + + @Test void testApplyTransforms_vatu() { diff --git a/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/abvs.txt b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/abvs.txt new file mode 100644 index 00000000000..091c6e3c527 --- /dev/null +++ b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/abvs.txt @@ -0,0 +1,7 @@ +दुर्ज्ञेय=343, 370, 521, 508, 352 +मार्द्विक=351, 367, 368, 640, 513, 326 +गयौँ=328, 352, 381, 306 +नाईँ=345, 367, 313, 306 +गरेँ=328, 353, 376, 306 +उर्लिँदो=314, 368, 355,513, 306, 343, 380 +चतुर्विंश=331, 341, 370, 368, 358, 514, 359 \ No newline at end of file diff --git a/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/akhn.txt b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/akhn.txt new file mode 100644 index 00000000000..82d8dfc4361 --- /dev/null +++ b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/akhn.txt @@ -0,0 +1,9 @@ +नीतिज्ञ=345,369,368,341,521 +क्षिप्त=368,520,642 +स्त्रियम्मन्य=368,658,352,547,351,541,352 +सर्वत्र=361,358,513,593 +ज्ञपित=521,368,347,341 +नेत्रशुष्कता=345,376,593,359,370,556,326,341,367 +खगोलशास्त्र=327,328,380,355,359,367,658 +यात्रान्त=352,367,593,367,541,341 +राजलक्षण=353,367,333,355,520,340 \ No newline at end of file diff --git a/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/blwf.txt b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/blwf.txt new file mode 100644 index 00000000000..4e400fb7e78 --- /dev/null +++ b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/blwf.txt @@ -0,0 +1,5 @@ +राष्ट्रिय=353, 367, 368, 650, 516, 352 +डाङ्री=338, 367, 330, 516, 369 +तङ्ङ्रनु=341, 526, 330, 516, 345, 370 +ड्रम=338, 516, 351 +ब्याट्री=545, 352, 367, 336, 516, 369 \ No newline at end of file diff --git a/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/blws.txt b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/blws.txt new file mode 100644 index 00000000000..696040d46c2 --- /dev/null +++ b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/blws.txt @@ -0,0 +1,4 @@ +दृष्टि=660, 368, 650 +रूख=662, 327 +रुद्र=661, 594 +ट्रू=336, 584 \ No newline at end of file diff --git a/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/cjct.txt b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/cjct.txt new file mode 100644 index 00000000000..2e5e2e83d97 --- /dev/null +++ b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/cjct.txt @@ -0,0 +1,8 @@ +अड्डा=310, 626, 367 +अङ्कुरित=310, 609, 370, 368, 353, 341 +पद्म=347, 638 +कट्ठा=326, 621, 367 +विद्यालय=368, 358, 639, 367, 355, 352 +उद्द्योतित=314, 539, 639, 380, 368, 341, 341 +उद्विज्न=314, 368, 640, 529, 345 +बुद्ध=349, 370, 634 \ No newline at end of file diff --git a/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/half.txt b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/half.txt new file mode 100644 index 00000000000..23042e4c9ed --- /dev/null +++ b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/half.txt @@ -0,0 +1,11 @@ +ऐकाग्र्य=312, 326, 367, 590, 382, 352 +कक्र्याक्क=326, 588, 382, 352, 367, 604 +लभ्र्याकलुभ्रुक=355, 598, 382, 352, 367, 326, 355, 370, 598, 370, 326 +बिछ्याउनु=368, 349, 528, 352, 367, 314, 345, 370 +क्याप्चो=522, 352, 367, 543, 331, 380 +खण्डेष्टिका=327, 536, 338, 376, 368, 650, 326, 367 +सौभाग्यसूत्र=361, 381, 350, 367, 524, 352, 361, 371, 593 +केस्र्याइ=326, 376, 601, 382, 352, 367, 312 +सूक्ष्मशरीर=361, 371, 567, 351, 359, 353, 369, 353 +वर्त्स्य‍=358, 537, 557, 352, 513, 436 +र्‍याक=587, 352, 367, 326 \ No newline at end of file diff --git a/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/haln.txt b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/haln.txt new file mode 100644 index 00000000000..1ea180a7475 --- /dev/null +++ b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/haln.txt @@ -0,0 +1,4 @@ +विद्वान्=368, 358, 640, 367, 345, 382 +गर्छन्=328, 332, 513, 345, 382 +विषयविद्=368, 358, 360, 352, 368, 358, 343, 382 +षट्=360, 336, 382 diff --git a/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/pres.txt b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/pres.txt new file mode 100644 index 00000000000..63fe3dae9d2 --- /dev/null +++ b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/pres.txt @@ -0,0 +1,9 @@ +अक्कड=310, 604, 338 +भक्त=350, 605 +वक्त्र=358, 522, 593 +छ्याए=528, 352, 367, 320 +हात्ती=362, 367, 519, 369 +प्राप्त=595, 367, 642 +असरल्ल=310, 361, 353, 645 +पश्चगमन=347, 646, 328, 351, 345 +आह्वान=311, 657, 367, 345 \ No newline at end of file diff --git a/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/rkrf.txt b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/rkrf.txt new file mode 100644 index 00000000000..bdc7752f1a8 --- /dev/null +++ b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/rkrf.txt @@ -0,0 +1,9 @@ +श्रीमान्=517,369,351,367,345,382 +दुष्प्रयास=343,370,556,595,352,367,361 +निष्क्रिय=368,345,368,556,588,352 +ह्रस्व=602,557,358 +खुन्रो=327,370,669,380 +त्यान्द्रो=537,352,367,541,594,380 +ख्रिस्टाब्दी=368,589,557,336,367,545,343,369 +ग्रन्थि=590,368,541,342 +प्रक्षेपण=595,520,376,347,340 \ No newline at end of file diff --git a/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/rphf.txt b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/rphf.txt new file mode 100644 index 00000000000..2db1b066a60 --- /dev/null +++ b/fontbox/src/test/resources/gsub.lohit_devanagari.dev2.nepali/rphf.txt @@ -0,0 +1,9 @@ +कर्त्री=326,593,577 +आर्द्रता=311,594,513,341,367 +अन्तर्ज्ञानी=310,541,341,521,367,513,345,369 +अन्तर्द्वन्द्व=310,541,341,640,513,541,640 +तेर्छ्याउनु=341,376,528,352,367,513,314,345,370 +सौहार्द्य=361,381,362,367,639,513 +शार्ङ्ग=359,367,611,513 +वर्त्स्य=358,537,557,352,513 +पार्श्विक=347,367,368,649,513,326 \ No newline at end of file diff --git a/generated.pdf b/generated.pdf new file mode 100644 index 00000000000..3c4795451c8 Binary files /dev/null and b/generated.pdf differ diff --git a/nepali.pdf b/nepali.pdf new file mode 100644 index 00000000000..ede6efacb92 Binary files /dev/null and b/nepali.pdf differ diff --git a/pdfbox/src/test/resources/org/apache/pdfbox/ttf/Nirmala.ttf b/pdfbox/src/test/resources/org/apache/pdfbox/ttf/Nirmala.ttf new file mode 100644 index 00000000000..e0de324f061 Binary files /dev/null and b/pdfbox/src/test/resources/org/apache/pdfbox/ttf/Nirmala.ttf differ diff --git a/pdfbox/src/test/resources/org/apache/pdfbox/ttf/NotoSansDevanagari.ttf b/pdfbox/src/test/resources/org/apache/pdfbox/ttf/NotoSansDevanagari.ttf new file mode 100644 index 00000000000..1a1271465ba Binary files /dev/null and b/pdfbox/src/test/resources/org/apache/pdfbox/ttf/NotoSansDevanagari.ttf differ diff --git a/pom.xml b/pom.xml index 77a65aab241..60f0e402f07 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,4 @@ - - + 4.0.0 @@ -43,27 +44,37 @@ examples - - scm:svn:https://svn.apache.org/repos/asf/pdfbox/trunk - scm:svn:https://svn.apache.org/repos/asf/pdfbox/trunk - https://svn.apache.org/viewvc/pdfbox/trunk - + + + + + org.apache.maven.plugin-tools + maven-plugin-annotations + 3.13.1 + + + org.json + json + 20210307 + + + - - - org.apache.rat - apache-rat-plugin - 0.16.1 - - - release.properties - .github/workflows/codeql-analysis.yml - - - - + + + org.apache.rat + apache-rat-plugin + 0.16.1 + + + release.properties + .github/workflows/codeql-analysis.yml + + + + @@ -71,8 +82,8 @@ apache-release - - + + @@ -130,7 +141,8 @@ - + From: ${username}@apache.org To: dev@pdfbox.apache.org @@ -174,10 +186,10 @@ A release vote template has been generated for you: - org.apache.ant - ant-nodeps - 1.8.1 - + org.apache.ant + ant-nodeps + 1.8.1 +