From 1110e2a01b3a1d3d099ea65d54378924e2d67f87 Mon Sep 17 00:00:00 2001 From: Andreas Date: Sat, 5 Jun 2021 11:22:05 +0200 Subject: [PATCH] Renaming and merging files. --- Makefile | 17 +- doc/config.ld | 3 +- flare-annot.lua | 572 ++++++++++++++++++++++++ flare-format-annot.lua | 604 -------------------------- flare-format-obj.lua => flare-obj.lua | 4 +- flare-page.lua | 3 +- test/Makefile | 15 +- test/test-flare-annot.lua | 136 ------ test/test-flare-annot.tex | 11 - test/test-flare-format-annot1.lua | 115 ++++- 10 files changed, 700 insertions(+), 780 deletions(-) delete mode 100644 flare-format-annot.lua rename flare-format-obj.lua => flare-obj.lua (99%) delete mode 100644 test/test-flare-annot.lua delete mode 100644 test/test-flare-annot.tex diff --git a/Makefile b/Makefile index 6325415..2958510 100644 --- a/Makefile +++ b/Makefile @@ -36,18 +36,17 @@ DIST-TEX-FILES= \ flare.sty DIST-LUA-FILES= \ - flare-action.lua \ - flare-annot.lua \ - flare-dest.lua \ - flare-doc.lua \ - flare-format-annot.lua \ - flare-format-obj.lua \ - flare-keyval.lua \ flare.lua \ + flare-pkg.lua \ + flare-types.lua \ flare-luatex.lua \ + flare-doc.lua \ flare-page.lua \ - flare-pkg.lua \ - flare-types.lua + flare-keyval.lua \ + flare-obj.lua \ + flare-annot.lua \ + flare-dest.lua \ + flare-action.lua DIST-DOC-FILES= \ README.md diff --git a/doc/config.ld b/doc/config.ld index 69760c6..177b440 100644 --- a/doc/config.ld +++ b/doc/config.ld @@ -22,11 +22,10 @@ file = { '../flare-page.lua', '../flare-keyval.lua', + '../flare-obj.lua', '../flare-annot.lua', '../flare-dest.lua', '../flare-action.lua', - '../flare-format-obj.lua', - '../flare-format-annot.lua', } diff --git a/flare-annot.lua b/flare-annot.lua index 2ed2bfe..c683450 100644 --- a/flare-annot.lua +++ b/flare-annot.lua @@ -26,6 +26,7 @@ local pdfdictionary = types.pdfdictionary local luatex = require('flare-luatex') + --- Annotations -- @section annotations @@ -105,4 +106,575 @@ function Page:insertAnnot(annot, objnum) end +--- Formats an arbitrary annotation. +-- Distributes work to more specialized formatting functions. +-- Returns a string to be used as the `data` field of a `pdf_annot` whatsit-node, +-- or `nil` if this annotation type is not supported. +-- @pdfe annot annotation dictionary +-- @number objnum object number of annotation +-- @return String +function Page:formatAnnotation(annot, objnum) + if self:getUserInput('@@@') then + return nil + end + local func = self:getAnnotFunc(annot) + if func then + local t = func(self, annot) + return self:formatTable(t) + else + pkg.warning( + string.format("Annotation of type '%s' not supported.", annot.Subtype)) + return nil + end +end + + +function Page:getAnnotFunc(annot) + return load('return getAnnot' .. annot.Subtype, nil, 't', self)() +end + + +--- Converts a Lua table into _one_ string. +-- Functions like `getAnnotText()`, `getAnnotCircle()`, ... return tables +-- whose keys are PDF dictionary keys and whose values are formatted strings. +-- This function converts such a table into a long string to be used +-- as the `data` field of a `pdf_annot` whatsit node. +-- @table t table +-- @boolean dict_tags internal flag, `nil` for initial call +-- @return Formatted string +function Page:formatTable(t, dict_tags) + local spairs = pairs + if _G._UNITTEST then + spairs = sorted_pairs + end + dict_tags = dict_tags or false + if type(t) == 'table' then + if t.type == 'array' then + local str = '' + for _, v in spairs(t) do + str = str .. ' ' .. self:formatTable(v, true) + end + return '[' .. str .. ' ]' + else + local str = '' + for k, v in spairs(t) do + str = string.format('%s /%s %s', str, k, self:formatTable(v, true)) + end + if dict_tags then + return '<<' .. str .. ' >>' + else + return str:sub(2) + end + end + else + return t + end +end + + +--- Returns common annotation entries. +-- @pdfe annot annotation dictionary +-- @number objnum object number of annotation +-- @return Table +function Page:getAnnotCommonEntries(annot, objnum) + return { + Subtype = self:getName(annot, 'Subtype'), + Contents = self:getString(annot, 'Contents'), + P = self:getP(annot, 'P'), + NM = self:getString(annot, 'NM'), + M = self:getString(annot, 'M'), + F = self:getInteger(annot, 'F'), + AP = self:getDictionary(annot, 'AP'), + AS = self:getName(annot, 'AS'), + Border = self:formatBorder(annot, true), + C = self:getArray(annot, 'C'), + StructParent = self:getInteger(annot, 'StructParent'), + OC = self:getDictionary(annot, 'OC'), + } +end + + +--- Returns markup annotation entries. +-- @pdfe annot annotation dictionary +-- @number objnum object number of annotation +-- @return Table +function Page:getAnnotMarkupEntries(annot, objnum) + return { + T = self:getString(annot, 'T'), + Popup = self:getDictionary(annot, 'Popup'), + CA = self:getNumber(annot, 'CA'), + RC = self:getStringOrStream(annot, 'RC'), + CreationDate = self:getString(annot, 'CreationDate'), + IRT = self:formatIRT(annot, objnum), + Subj = self:getString(annot, 'Subj'), + RT = self:getName(annot, 'RT'), + IT = self:getName(annot, 'IT'), + ExData = self:getDictionary(annot, 'ExData'), + } +end + + +--- Formats the value of a `/P` entry, eg: `4 0 R` +-- @return Formatted string +function Page:getP() + -- Does it make sense to provide user input for /P at all? + local user = self:getUserInput('P') + if type(user) == 'string' then + return user + else + return string.format('%s 0 R', pdf.getpageref(self.page)) + end +end + + +--- Formats the value of an `/IRT` entry, eg: `4 0 R` +-- @pdfe annot annotation dictionary +-- @number objnum object number of annotation +-- @return Formatted string +function Page:formatIRT(annot, objnum) + local ptype, pval, objnum_orig = pdfe.getfromdictionary(annot, 'IRT') + if ptype == nil then + return nil + end + if ptype ~= luatex.pdfeObjType.reference then + -- PDF spec says that /IRT is a dictionary! Really? Hmm ... + -- Let's suppose that /IRT is a reference, wait for + -- real world PDFs, and issue a warning for now: + pkg.warning( + string.format('/IRT shall be a reference, but is type %s', ptype)) + pkg.bugs() + return nil + end + local annot_obj_new = self:findFromCache_AnnotObjNew(objnum) + if annot_obj_new == nil then + return nil + else + return string.format('%d 0 R', annot_obj_new) + end +end + + +--- Returns a `Square` annotation dictionary. +-- @pdfe annot annotation dictionary +-- @return Table +function Page:getAnnotSquare(annot) + local t = { + BS = self:getDictionary(annot, 'BS'), + IC = self:getArray(annot, 'IC'), + BE = self:getDictionary(annot, 'BE'), + RD = self:getArray(annot, 'RD'), + } + self:appendTable(t, self:getAnnotCommonEntries(annot)) + self:appendTable(t, self:getAnnotMarkupEntries(annot)) + return t +end + + +--- Returns a `Circle` annotation dictionary. +-- @pdfe annot annotation dictionary +-- @return Table +function Page:getAnnotCircle(annot) + return self:getAnnotSquare(annot) +end + + +--- Returns a a `Text` annotation dictionary. +-- @pdfe annot annotation dictionary +-- @return Table +function Page:getAnnotText(annot) + local t = { + Open = self:getBoolean(annot, 'Open'), + Name = self:getName(annot, 'Name'), + State = self:getString(annot, 'State'), + StateModel = self:getString(annot, 'StateModel'), + } + self:appendTable(t, self:getAnnotCommonEntries(annot)) + self:appendTable(t, self:getAnnotMarkupEntries(annot)) + return t +end + + +--- Return an array of coordinates, eg. `QuadPoints`. +-- @pdfe annot annotation dictionary +-- @string key key +-- @return Table +function Page:getCoordinatesArray(annot, key) + if annot[key] then + local ctm = self:readFromCache('ctm') + ctm = ctm or self.IdentityCTM + + local coords = annot[key] + local newcoords = types.pdfarray:new() + local idx = 1 + while idx <= #coords do + local x, y = coords[idx], coords[idx + 1] + local xn, yn = self:applyCTM(ctm, x, y) + newcoords[#newcoords + 1] = xn + newcoords[#newcoords + 1] = yn + idx = idx + 2 + end + return newcoords + else + return nil + end +end + + +--- Returns a `Text Markup` annotion dictionary. +-- @pdfe annot annotation dictionary. +-- @return Table +function Page:getAnnotTextMarkup(annot) + local t = { + QuadPoints = self:getCoordinatesArray(annot, 'QuadPoints') + } + self:appendTable(t, self:getAnnotCommonEntries(annot)) + self:appendTable(t, self:getAnnotMarkupEntries(annot)) + return t +end + + +--- Returns a `FreeText` annotation dictionary. +-- @pdfe annot annotation dictionary +-- @return Table +function Page:getAnnotFreeText(annot) + local t = { + DA = self:getString(annot, 'DA'), + Q = self:getInteger(annot, 'Q'), + RC = self:getStringOrStream(annot, 'RC'), + DS = self:getString(annot, 'DS'), + CL = self:getCoordinatesArray(annot, 'CL'), + IT = self:getName(annot, 'IT'), + BE = self:getDictionary(annot, 'BE'), + RD = self:getAnnotFreeText_RD(annot, 'RD'), + BS = self:getDictionary(annot, 'BS'), + LE = self:getDictionary(annot, 'LE'), + } + self:appendTable(t, self:getAnnotCommonEntries(annot)) + self:appendTable(t, self:getAnnotMarkupEntries(annot)) + return t +end + + +--- Returns the `RD` rectangle of a `FreeText` annotation. +-- @pdfe obj dictionary +-- @string key key +-- @return Table +function Page:getAnnotFreeText_RD(obj, key) + local array = obj[key] + if not array then + return nil + end + local ctm = self:readFromCache('ctm') + ctm = ctm or self.IdentityCTM + local scale = 0.5 * (ctm.a + ctm.d) + local t = pdfarray:new() + for idx = 1, #array do + t[#t + 1] = array[idx] * scale + end + return t +end + + +--- Returns a `Link` annotation dictionary. +-- @pdfe annot annotation dictionary +-- @return Table +function Page:getAnnotLink(annot) + local t = { + A = self:getAction(annot['A']), + -- Dest + H = self:getName(annot, 'H'), + PA = self:getDictionary(annot, 'PA'), + QuadPoints = self:getCoordinatesArray(annot, 'QuadPoints'), + } + self:appendTable(t, self:getAnnotCommonEntries(annot)) + return t +end + + +--- Returns a `Line` annotation dicationary. +-- @pdfe annot annotation dictionary +-- @return Table +function Page:getAnnotLine(annot) + local t = { + L = self:getCoordinatesArray(annot, 'L'), + BS = self:getDictionary(annot, 'BS'), + LE = self:getArray(annot, 'LE'), + IC = self:getArray(annot, 'IC'), + LL = self:getNumber(annot, 'LL', true), + LLE = self:getNumber(annot, 'LLE', true), + Cap = self:getBoolean(annot, 'Cap'), + IT = self:getName(annot, 'IT'), + LLO = self:getNumber(annot, 'LLO', true), + CP = self:getName(annot, 'CP'), + -- TODO: Measure dictionary must be adjusted by the scaling factor + Measure = self:getDictionary(annot, 'Measure'), + CO = self:getArray(annot, 'CO', true), + } + self:appendTable(t, self:getAnnotCommonEntries(annot)) + self:appendTable(t, self:getAnnotMarkupEntries(annot)) + return t +end + + +--- Returns a `Highlight` annotation dictionary. +-- @pdfe annot annotation dictionary. +-- @return Table +function Page:getAnnotHighlight(annot) + return self:getAnnotTextMarkup(annot) +end + + +--- Returns an `Underline` annotation dictionary. +-- @pdfe annot annotation dictionary +-- @return Table +function Page:getAnnotUnderline(annot) + return self:getAnnotTextMarkup(annot) +end + + +--- Returns a `StrikeOut` annotation dictionary. +-- @pdfe annot annotation dictionary +-- @return Table +function Page:getAnnotStrikeOut(annot) + return self:getAnnotTextMarkup(annot) +end + + +--- Returns a `Squiggly` annotation dictionary. +-- @pdfe annot annotation dictionary +-- @return Table +function Page:getAnnotSquiggly(annot) + return self:getAnnotTextMarkup(annot) +end + + +--- Returns a `FileAttachment` annotation table. +-- @pdfe annot annotation dictionary +-- @return Table. +function Page:getAnnotFileAttachment(annot) + local t = { + FS = self:getFileSpecification(annot, 'FS'), + Name = self:getName(annot, 'Name'), + } + self:appendTable(t, self:getAnnotCommonEntries(annot)) + self:appendTable(t, self:getAnnotMarkupEntries(annot)) + return t +end + + +--- Returns an `FS` (file specification) dicationary. +-- @pdfe dict dictionary +-- @string key key +-- @return Table +function Page:getFileSpecification(dict, key) + -- file spec string + local str = pdfe.getstring(dict, key) + if str then + return str + end + -- file spec dictionary + local dict = pdfe.getdictionary(dict, key) + local t = { + Type = '/Filespec', + FS = self:getName(dict, 'FS'), + F = self:getString(dict, 'F'), + UF = self:getString(dict, 'UF'), + DOS = self:getString(dict, 'DOS'), + Mac = self:getString(dict, 'Mac'), + Unix = self:getString(dict, 'Unix'), + ID = self:getArray(dict, 'ID'), + V = self:getBoolean(dict, 'V'), + EF = self:getEmbeddedFileDict(dict, 'EF'), + RF = self:getRelatedFileDict(dict, 'RF'), + Desc = self:getString(dict, 'Desc'), + CI = self:getDictionary(dict, 'CI'), + } + return t +end + + +--- Returns an `EF` (embedded file) dicionary +-- @pdfe dict dictionary +-- @string key key +-- @return Table +function Page:getEmbeddedFileDict(dict, key) + dict = pdfe.getdictionary(dict, key) + if dict == nil then + return nil + end + local t = { + F = self:getEmbeddedFileStreamDict(dict, 'F'), + UF = self:getEmbeddedFileStreamDict(dict, 'UF'), + DOS = self:getEmbeddedFileStreamDict(dict, 'DOS'), + Mac= self:getEmbeddedFileStreamDict(dict, 'Mac'), + Unix = self:getEmbeddedFileStreamDict(dict, 'Unix'), + } + return t +end + + +--- Returns an embedded file stream dictionary. +-- @pdfe dict dictionary +-- @string key key +-- @return Table +function Page:getEmbeddedFileStreamDict(dict, key) + if pdfe.type(dict[key]) == 'pdfe.stream' then + -- TODO: do not return an arbitrary stream, but a stream + -- with the special stream dictionary of Page:getEmbeddedFileParams() + return self:getStream(dict, key) + else + return nil + end +end + + +--- Returns an embedded file parameter dictionary. +-- @pdfe dict dictionary +-- @string key key +-- @return table +function Page:getEmbeddedFileParams(dict, key) + dict = pdfe.getdictionary(dict, key) + if dict == nil then + return nil + end + local t = { + Size = self:getInteger(dict, 'Size'), + CreationDate = self:getString(dict, 'CreationDate'), + ModDate = self:getString(dict, 'ModDate'), + Mac = self:getMacFileInfo(dict, 'Mac'), + CheckSum = self:getString(dict, 'CheckSum'), + } + return t +end + + +--- Returns a Mac file info dictionary. +-- @pdfe dict dictionary +-- @string key key +-- @return Table +function Page:getMacFileInfo(dict, key) + dict = pdfe.getdictionary(dict, key) + if dict == nil then + return nil + end + local t = { + Subtype = self:getInteger(dict, 'Subtype'), + Creator = self:getInteger(dict, 'Creator'), + ResFork = self:getStream(dict, 'ResFork'), + } + return t +end + + +--- Returns an `RF` (related files) dictionary. +-- @pdfe dict dictionary +-- @string key key +-- @return Table +function Page:getRelatedFileDict(dict, key) + dict = pdfe.getdictionary(dict, key) + if dict == nil then + return nil + end + local t = { + F = self:getRelatedFileArray(dict['F']), + UF = self:getRelatedFileArray(dict['UF']), + DOS = self:getRelatedFileArray(dict['DOS']), + Mac= self:getRelatedFileArray(dict['Mac']), + Unix = self:getRelatedFileArray(dict['Unix']), + } + return t +end + + +--- Returns a file array (an entry of an `RF` dictionary). +-- @pdfe array array +-- @return table +function Page:getRelatedFileArray(array) + local t = types.pdfarray:new() + for idx = 1, #array, 2 do + t[#t + 1] = self:getString(array, idx) + t[#t + 1] = self:getStream(array, idx + 1) + end +end + + +--- Formats a `/Border` item. +-- @pdfe annot annotation dictionary +-- @numbool scale scaling factor +-- @return Formatted string +function Page:formatBorder(annot, scale) + scale = scale or false + local user = self:getUserInput('Border') + if type(user) == 'string' then + return user + end + if type(user) == 'table' and user.op == 'scale' then + scale = user.val + end + return self:formatBorder_hlp(annot, scale) +end + + +--- Helper function for @{Page:formatBorder} which scales +-- a `/Border` array. +-- @pdfe annot aAnnotation dictionary +-- @numbool scale scaling factor +-- @return Formatted string +function Page:formatBorder_hlp(annot, scale) + local border = annot.Border + if border then + local hradius = pdfe.getnumber(border, 0) + local vradius = pdfe.getnumber(border, 1) + local width = pdfe.getnumber(border, 2) + local dash = pdfe.getarray(border, 3) + dash = self:formatBorderDash(dash, scale) + return string.format('[ %.5g %.5g %.5g %s]', + self:scaleNumber(hradius, scale), + self:scaleNumber(vradius, scale), + self:scaleNumber(width, scale), + dash) + else + return nil + end +end + + +--- Formats a border dash item. +-- @pdfe dash pdfe array of dash +-- @numbool scale scaling factor +-- @return Formated string +function Page:formatBorderDash(dash, scale) + local user = self:getUserInput('BorderDash') + if type(user) == 'string' then + return user + end + if type(user) == 'table' and user.op == 'scale' then + scale = user.val + end + if dash then + local val1 = pdfe.getnumber(dash, 0) + local val2 = pdfe.getnumber(dash, 1) + return string.format('[ %.5g %.5g ]', + self:scaleNumber(val1, scale), + self:scaleNumber(val2, scale)) + else + return '' + end +end + + +--- Auxiliary  +-- @section Auxiliary  + + +--- Appends table `tab2` to table `tab1`. +-- @table tab1 +-- @table tab2 +function Page:appendTable(tab1, tab2) + for k, v in pairs(tab2) do + tab1[k] = v + end +end + + return Page diff --git a/flare-format-annot.lua b/flare-format-annot.lua deleted file mode 100644 index cbf1edd..0000000 --- a/flare-format-annot.lua +++ /dev/null @@ -1,604 +0,0 @@ --- --- Copyright 2021 Andreas MATTHIAS --- --- This work may be distributed and/or modified under the --- conditions of the LaTeX Project Public License, either version 1.3c --- of this license or (at your option) any later version. --- The latest version of this license is in --- http://www.latex-project.org/lppl.txt --- and version 1.3c or later is part of all distributions of LaTeX --- version 2008 or later. --- --- This work has the LPPL maintenance status `maintained'. --- --- The Current Maintainer of this work is Andreas MATTHIAS. --- - - ---- --- @classmod Page -local Page = {} - -local pkg = require('flare-pkg') -local types = require('flare-types') -local pdfarray = types.pdfarray -local pdfdictionary = types.pdfdictionary -local luatex = require('flare-luatex') - - ---- Formatting Annotations --- @section formatting_annotations - - ---- Formats an arbitrary annotation. --- Distributes work to more specialized formatting functions. --- Returns a string to be used as the `data` field of a `pdf_annot` whatsit-node, --- or `nil` if this annotation type is not supported. --- @pdfe annot annotation dictionary --- @number objnum object number of annotation --- @return String -function Page:formatAnnotation(annot, objnum) - if self:getUserInput('@@@') then - return nil - end - local func = self:getAnnotFunc(annot) - if func then - local t = func(self, annot) - return self:formatTable(t) - else - pkg.warning( - string.format("Annotation of type '%s' not supported.", annot.Subtype)) - return nil - end -end - - -function Page:getAnnotFunc(annot) - return load('return getAnnot' .. annot.Subtype, nil, 't', self)() -end - - ---- Converts a Lua table into _one_ string. --- Functions like `getAnnotText()`, `getAnnotCircle()`, ... return tables --- whose keys are PDF dictionary keys and whose values are formatted strings. --- This function converts such a table into a long string to be used --- as the `data` field of a `pdf_annot` whatsit node. --- @table t table --- @boolean dict_tags internal flag, `nil` for initial call --- @return Formatted string -function Page:formatTable(t, dict_tags) - local spairs = pairs - if _G._UNITTEST then - spairs = sorted_pairs - end - dict_tags = dict_tags or false - if type(t) == 'table' then - if t.type == 'array' then - local str = '' - for _, v in spairs(t) do - str = str .. ' ' .. self:formatTable(v, true) - end - return '[' .. str .. ' ]' - else - local str = '' - for k, v in spairs(t) do - str = string.format('%s /%s %s', str, k, self:formatTable(v, true)) - end - if dict_tags then - return '<<' .. str .. ' >>' - else - return str:sub(2) - end - end - else - return t - end -end - - ---- Returns common annotation entries. --- @pdfe annot annotation dictionary --- @number objnum object number of annotation --- @return Table -function Page:getAnnotCommonEntries(annot, objnum) - return { - Subtype = self:getName(annot, 'Subtype'), - Contents = self:getString(annot, 'Contents'), - P = self:getP(annot, 'P'), - NM = self:getString(annot, 'NM'), - M = self:getString(annot, 'M'), - F = self:getInteger(annot, 'F'), - AP = self:getDictionary(annot, 'AP'), - AS = self:getName(annot, 'AS'), - Border = self:formatBorder(annot, true), - C = self:getArray(annot, 'C'), - StructParent = self:getInteger(annot, 'StructParent'), - OC = self:getDictionary(annot, 'OC'), - } -end - - ---- Returns markup annotation entries. --- @pdfe annot annotation dictionary --- @number objnum object number of annotation --- @return Table -function Page:getAnnotMarkupEntries(annot, objnum) - return { - T = self:getString(annot, 'T'), - Popup = self:getDictionary(annot, 'Popup'), - CA = self:getNumber(annot, 'CA'), - RC = self:getStringOrStream(annot, 'RC'), - CreationDate = self:getString(annot, 'CreationDate'), - IRT = self:formatIRT(annot, objnum), - Subj = self:getString(annot, 'Subj'), - RT = self:getName(annot, 'RT'), - IT = self:getName(annot, 'IT'), - ExData = self:getDictionary(annot, 'ExData'), - } -end - - ---- Formats the value of a `/P` entry, eg: `4 0 R` --- @return Formatted string -function Page:getP() - -- Does it make sense to provide user input for /P at all? - local user = self:getUserInput('P') - if type(user) == 'string' then - return user - else - return string.format('%s 0 R', pdf.getpageref(self.page)) - end -end - - ---- Formats the value of an `/IRT` entry, eg: `4 0 R` --- @pdfe annot annotation dictionary --- @number objnum object number of annotation --- @return Formatted string -function Page:formatIRT(annot, objnum) - local ptype, pval, objnum_orig = pdfe.getfromdictionary(annot, 'IRT') - if ptype == nil then - return nil - end - if ptype ~= luatex.pdfeObjType.reference then - -- PDF spec says that /IRT is a dictionary! Really? Hmm ... - -- Let's suppose that /IRT is a reference, wait for - -- real world PDFs, and issue a warning for now: - pkg.warning( - string.format('/IRT shall be a reference, but is type %s', ptype)) - pkg.bugs() - return nil - end - local annot_obj_new = self:findFromCache_AnnotObjNew(objnum) - if annot_obj_new == nil then - return nil - else - return string.format('%d 0 R', annot_obj_new) - end -end - - ---- Returns a `Square` annotation dictionary. --- @pdfe annot annotation dictionary --- @return Table -function Page:getAnnotSquare(annot) - local t = { - BS = self:getDictionary(annot, 'BS'), - IC = self:getArray(annot, 'IC'), - BE = self:getDictionary(annot, 'BE'), - RD = self:getArray(annot, 'RD'), - } - self:appendTable(t, self:getAnnotCommonEntries(annot)) - self:appendTable(t, self:getAnnotMarkupEntries(annot)) - return t -end - - ---- Returns a `Circle` annotation dictionary. --- @pdfe annot annotation dictionary --- @return Table -function Page:getAnnotCircle(annot) - return self:getAnnotSquare(annot) -end - - ---- Returns a a `Text` annotation dictionary. --- @pdfe annot annotation dictionary --- @return Table -function Page:getAnnotText(annot) - local t = { - Open = self:getBoolean(annot, 'Open'), - Name = self:getName(annot, 'Name'), - State = self:getString(annot, 'State'), - StateModel = self:getString(annot, 'StateModel'), - } - self:appendTable(t, self:getAnnotCommonEntries(annot)) - self:appendTable(t, self:getAnnotMarkupEntries(annot)) - return t -end - - ---- Return an array of coordinates, eg. `QuadPoints`. --- @pdfe annot annotation dictionary --- @string key key --- @return Table -function Page:getCoordinatesArray(annot, key) - if annot[key] then - local ctm = self:readFromCache('ctm') - ctm = ctm or self.IdentityCTM - - local coords = annot[key] - local newcoords = types.pdfarray:new() - local idx = 1 - while idx <= #coords do - local x, y = coords[idx], coords[idx + 1] - local xn, yn = self:applyCTM(ctm, x, y) - newcoords[#newcoords + 1] = xn - newcoords[#newcoords + 1] = yn - idx = idx + 2 - end - return newcoords - else - return nil - end -end - - ---- Returns a `Text Markup` annotion dictionary. --- @pdfe annot annotation dictionary. --- @return Table -function Page:getAnnotTextMarkup(annot) - local t = { - QuadPoints = self:getCoordinatesArray(annot, 'QuadPoints') - } - self:appendTable(t, self:getAnnotCommonEntries(annot)) - self:appendTable(t, self:getAnnotMarkupEntries(annot)) - return t -end - - ---- Returns a `FreeText` annotation dictionary. --- @pdfe annot annotation dictionary --- @return Table -function Page:getAnnotFreeText(annot) - local t = { - DA = self:getString(annot, 'DA'), - Q = self:getInteger(annot, 'Q'), - RC = self:getStringOrStream(annot, 'RC'), - DS = self:getString(annot, 'DS'), - CL = self:getCoordinatesArray(annot, 'CL'), - IT = self:getName(annot, 'IT'), - BE = self:getDictionary(annot, 'BE'), - RD = self:getAnnotFreeText_RD(annot, 'RD'), - BS = self:getDictionary(annot, 'BS'), - LE = self:getDictionary(annot, 'LE'), - } - self:appendTable(t, self:getAnnotCommonEntries(annot)) - self:appendTable(t, self:getAnnotMarkupEntries(annot)) - return t -end - - ---- Returns the `RD` rectangle of a `FreeText` annotation. --- @pdfe obj dictionary --- @string key key --- @return Table -function Page:getAnnotFreeText_RD(obj, key) - local array = obj[key] - if not array then - return nil - end - local ctm = self:readFromCache('ctm') - ctm = ctm or self.IdentityCTM - local scale = 0.5 * (ctm.a + ctm.d) - local t = pdfarray:new() - for idx = 1, #array do - t[#t + 1] = array[idx] * scale - end - return t -end - - ---- Returns a `Link` annotation dictionary. --- @pdfe annot annotation dictionary --- @return Table -function Page:getAnnotLink(annot) - local t = { - A = self:getAction(annot['A']), - -- Dest - H = self:getName(annot, 'H'), - PA = self:getDictionary(annot, 'PA'), - QuadPoints = self:getCoordinatesArray(annot, 'QuadPoints'), - } - self:appendTable(t, self:getAnnotCommonEntries(annot)) - return t -end - - ---- Returns a `Line` annotation dicationary. --- @pdfe annot annotation dictionary --- @return Table -function Page:getAnnotLine(annot) - local t = { - L = self:getCoordinatesArray(annot, 'L'), - BS = self:getDictionary(annot, 'BS'), - LE = self:getArray(annot, 'LE'), - IC = self:getArray(annot, 'IC'), - LL = self:getNumber(annot, 'LL', true), - LLE = self:getNumber(annot, 'LLE', true), - Cap = self:getBoolean(annot, 'Cap'), - IT = self:getName(annot, 'IT'), - LLO = self:getNumber(annot, 'LLO', true), - CP = self:getName(annot, 'CP'), - -- TODO: Measure dictionary must be adjusted by the scaling factor - Measure = self:getDictionary(annot, 'Measure'), - CO = self:getArray(annot, 'CO', true), - } - self:appendTable(t, self:getAnnotCommonEntries(annot)) - self:appendTable(t, self:getAnnotMarkupEntries(annot)) - return t -end - - ---- Returns a `Highlight` annotation dictionary. --- @pdfe annot annotation dictionary. --- @return Table -function Page:getAnnotHighlight(annot) - return self:getAnnotTextMarkup(annot) -end - - ---- Returns an `Underline` annotation dictionary. --- @pdfe annot annotation dictionary --- @return Table -function Page:getAnnotUnderline(annot) - return self:getAnnotTextMarkup(annot) -end - - ---- Returns a `StrikeOut` annotation dictionary. --- @pdfe annot annotation dictionary --- @return Table -function Page:getAnnotStrikeOut(annot) - return self:getAnnotTextMarkup(annot) -end - - ---- Returns a `Squiggly` annotation dictionary. --- @pdfe annot annotation dictionary --- @return Table -function Page:getAnnotSquiggly(annot) - return self:getAnnotTextMarkup(annot) -end - - ---- Returns a `FileAttachment` annotation table. --- @pdfe annot annotation dictionary --- @return Table. -function Page:getAnnotFileAttachment(annot) - local t = { - FS = self:getFileSpecification(annot, 'FS'), - Name = self:getName(annot, 'Name'), - } - self:appendTable(t, self:getAnnotCommonEntries(annot)) - self:appendTable(t, self:getAnnotMarkupEntries(annot)) - return t -end - - ---- Returns an `FS` (file specification) dicationary. --- @pdfe dict dictionary --- @string key key --- @return Table -function Page:getFileSpecification(dict, key) - -- file spec string - local str = pdfe.getstring(dict, key) - if str then - return str - end - -- file spec dictionary - local dict = pdfe.getdictionary(dict, key) - local t = { - Type = '/Filespec', - FS = self:getName(dict, 'FS'), - F = self:getString(dict, 'F'), - UF = self:getString(dict, 'UF'), - DOS = self:getString(dict, 'DOS'), - Mac = self:getString(dict, 'Mac'), - Unix = self:getString(dict, 'Unix'), - ID = self:getArray(dict, 'ID'), - V = self:getBoolean(dict, 'V'), - EF = self:getEmbeddedFileDict(dict, 'EF'), - RF = self:getRelatedFileDict(dict, 'RF'), - Desc = self:getString(dict, 'Desc'), - CI = self:getDictionary(dict, 'CI'), - } - return t -end - - ---- Returns an `EF` (embedded file) dicionary --- @pdfe dict dictionary --- @string key key --- @return Table -function Page:getEmbeddedFileDict(dict, key) - dict = pdfe.getdictionary(dict, key) - if dict == nil then - return nil - end - local t = { - F = self:getEmbeddedFileStreamDict(dict, 'F'), - UF = self:getEmbeddedFileStreamDict(dict, 'UF'), - DOS = self:getEmbeddedFileStreamDict(dict, 'DOS'), - Mac= self:getEmbeddedFileStreamDict(dict, 'Mac'), - Unix = self:getEmbeddedFileStreamDict(dict, 'Unix'), - } - return t -end - - ---- Returns an embedded file stream dictionary. --- @pdfe dict dictionary --- @string key key --- @return Table -function Page:getEmbeddedFileStreamDict(dict, key) - if pdfe.type(dict[key]) == 'pdfe.stream' then - -- TODO: do not return an arbitrary stream, but a stream - -- with the special stream dictionary of Page:getEmbeddedFileParams() - return self:getStream(dict, key) - else - return nil - end -end - - ---- Returns an embedded file parameter dictionary. --- @pdfe dict dictionary --- @string key key --- @return table -function Page:getEmbeddedFileParams(dict, key) - dict = pdfe.getdictionary(dict, key) - if dict == nil then - return nil - end - local t = { - Size = self:getInteger(dict, 'Size'), - CreationDate = self:getString(dict, 'CreationDate'), - ModDate = self:getString(dict, 'ModDate'), - Mac = self:getMacFileInfo(dict, 'Mac'), - CheckSum = self:getString(dict, 'CheckSum'), - } - return t -end - - ---- Returns a Mac file info dictionary. --- @pdfe dict dictionary --- @string key key --- @return Table -function Page:getMacFileInfo(dict, key) - dict = pdfe.getdictionary(dict, key) - if dict == nil then - return nil - end - local t = { - Subtype = self:getInteger(dict, 'Subtype'), - Creator = self:getInteger(dict, 'Creator'), - ResFork = self:getStream(dict, 'ResFork'), - } - return t -end - - ---- Returns an `RF` (related files) dictionary. --- @pdfe dict dictionary --- @string key key --- @return Table -function Page:getRelatedFileDict(dict, key) - dict = pdfe.getdictionary(dict, key) - if dict == nil then - return nil - end - local t = { - F = self:getRelatedFileArray(dict['F']), - UF = self:getRelatedFileArray(dict['UF']), - DOS = self:getRelatedFileArray(dict['DOS']), - Mac= self:getRelatedFileArray(dict['Mac']), - Unix = self:getRelatedFileArray(dict['Unix']), - } - return t -end - - ---- Returns a file array (an entry of an `RF` dictionary). --- @pdfe array array --- @return table -function Page:getRelatedFileArray(array) - local t = types.pdfarray:new() - for idx = 1, #array, 2 do - t[#t + 1] = self:getString(array, idx) - t[#t + 1] = self:getStream(array, idx + 1) - end -end - - ---- Formats a `/Border` item. --- @pdfe annot annotation dictionary --- @numbool scale scaling factor --- @return Formatted string -function Page:formatBorder(annot, scale) - scale = scale or false - local user = self:getUserInput('Border') - if type(user) == 'string' then - return user - end - if type(user) == 'table' and user.op == 'scale' then - scale = user.val - end - return self:formatBorder_hlp(annot, scale) -end - - ---- Helper function for @{Page:formatBorder} which scales --- a `/Border` array. --- @pdfe annot aAnnotation dictionary --- @numbool scale scaling factor --- @return Formatted string -function Page:formatBorder_hlp(annot, scale) - local border = annot.Border - if border then - local hradius = pdfe.getnumber(border, 0) - local vradius = pdfe.getnumber(border, 1) - local width = pdfe.getnumber(border, 2) - local dash = pdfe.getarray(border, 3) - dash = self:formatBorderDash(dash, scale) - return string.format('[ %.5g %.5g %.5g %s]', - self:scaleNumber(hradius, scale), - self:scaleNumber(vradius, scale), - self:scaleNumber(width, scale), - dash) - else - return nil - end -end - - ---- Formats a border dash item. --- @pdfe dash pdfe array of dash --- @numbool scale scaling factor --- @return Formated string -function Page:formatBorderDash(dash, scale) - local user = self:getUserInput('BorderDash') - if type(user) == 'string' then - return user - end - if type(user) == 'table' and user.op == 'scale' then - scale = user.val - end - if dash then - local val1 = pdfe.getnumber(dash, 0) - local val2 = pdfe.getnumber(dash, 1) - return string.format('[ %.5g %.5g ]', - self:scaleNumber(val1, scale), - self:scaleNumber(val2, scale)) - else - return '' - end -end - - ---- Auxiliary  --- @section Auxiliary  - - ---- Appends table `tab2` to table `tab1`. --- @table tab1 --- @table tab2 -function Page:appendTable(tab1, tab2) - for k, v in pairs(tab2) do - tab1[k] = v - end -end - - -return Page diff --git a/flare-format-obj.lua b/flare-obj.lua similarity index 99% rename from flare-format-obj.lua rename to flare-obj.lua index 1d9684d..cea2ae6 100644 --- a/flare-format-obj.lua +++ b/flare-obj.lua @@ -33,8 +33,8 @@ local ignoredKeys = { } ---- Formatting Objects --- @section formatting_objects +--- Basic types +-- @section basic types --- Returns a boolean value. diff --git a/flare-page.lua b/flare-page.lua index 721e41a..55ad4eb 100644 --- a/flare-page.lua +++ b/flare-page.lua @@ -28,8 +28,7 @@ local function require_sub(mod, name) end require_sub(Page, 'flare-keyval') -require_sub(Page, 'flare-format-obj') -require_sub(Page, 'flare-format-annot') +require_sub(Page, 'flare-obj') require_sub(Page, 'flare-annot') require_sub(Page, 'flare-dest') require_sub(Page, 'flare-action') diff --git a/test/Makefile b/test/Makefile index 2ae9e33..fe18239 100644 --- a/test/Makefile +++ b/test/Makefile @@ -3,30 +3,29 @@ TEST_FILES = \ test-flare-types.lua \ test-flare-doc.lua \ test-flare-page.lua \ - test-flare-dest.lua \ - test-flare-annot.lua \ test-flare-keyval.lua \ - test-flare-action.lua \ test-flare-format-obj1.lua \ test-flare-format-obj2.lua \ test-flare-format-annot1.lua \ test-flare-format-annot2.lua \ - test-flare-format-annot3.lua + test-flare-format-annot3.lua \ + test-flare-dest.lua \ + test-flare-action.lua MODULE_FILES = \ flare.sty \ flare.lua \ flare-pkg.lua \ flare-types.lua \ + flare-luatex.lua \ flare-doc.lua \ flare-page.lua \ flare-keyval.lua \ + flare-obj.lua \ + flare-annot.lua \ flare-annot.lua \ flare-dest.lua \ - flare-action.lua \ - flare-luatex.lua \ - flare-format-obj.lua \ - flare-format-annot.lua + flare-action.lua .PHONY: test test: $(TEST_FILES:.lua=.run) diff --git a/test/test-flare-annot.lua b/test/test-flare-annot.lua deleted file mode 100644 index 16d6e66..0000000 --- a/test/test-flare-annot.lua +++ /dev/null @@ -1,136 +0,0 @@ --- --- Copyright 2021 Andreas MATTHIAS --- --- This work may be distributed and/or modified under the --- conditions of the LaTeX Project Public License, either version 1.3c --- of this license or (at your option) any later version. --- The latest version of this license is in --- http://www.latex-project.org/lppl.txt --- and version 1.3c or later is part of all distributions of LaTeX --- version 2008 or later. --- --- This work has the LPPL maintenance status `maintained'. --- --- The Current Maintainer of this work is Andreas MATTHIAS. --- - -local loader = require('luapackageloader') -loader.add_lua_searchers() - -local bh = require('busted-helper') -bh.remove_unknown_args() - -require('busted.runner')({output = 'utfTerminal'}) -require('my_assertions') -assert:set_parameter('TableFormatLevel', -1) -print() - -luatex = require('flare-luatex') -flare = require('flare') -Doc = flare.Doc -Page = flare.Page -types = flare.types -pkg = require('flare-pkg') -pp = pkg.pp - -stringio = require('pl.stringio') -nt = require('nodetree') - - -describe('Testing flare-annot.lua:', function() - -test('Page:getAnnots()', - function() - local d = Doc:new() - local p = Page:new(d) - p:setGinKV('filename', 'pdf/circle-01.pdf') - p:setGinKV('page', 1) - p:openFile() - local annots = p:getAnnots() - assert.same(1, #annots) - local pt, pv, pd = pdfe.getfromarray(annots, 1) - assert.same(10, pt) - assert.same('pdfe.reference', pdfe.type(pv)) - assert.same(6, pd) -end) - -end) --describe - - -describe('Testing flare-annot.lua:', function() - -before_each(function() - orig_node_write = _G.node.write - _G.flare_box = nil - _G.node.write = function(val) _G.flare_box = val end - - orig_io_write = _G.io.write - _G.flare_stream = stringio.create() - _G.io.write = function(val) _G.flare_stream:write(val) end -end) - -after_each(function() - _G.node.write = orig_node_write - _G.io.write = orig_io_write -end) - -local function sanitize_node_str(str) - str = str:gsub(' objnum: %d+,', '') - str = str:gsub(', data: .*', '') - return str -end - - -test('Page:insertAnnot()', - function() - local d = Doc:new() - local p = Page:new(d) - p:setGinKV('filename', 'pdf/circle-01.pdf') - p:setGinKV('page', 1) - p:openFile() - local annot = pdfe.getpage(p.pdf, 1).Annots[1] - p.annotId = 1 - p:insertAnnot(annot) - - local nt = require('nodetree') - nt.print(_G.flare_box, - {verbosity=0, color='no', unit='bp', decimalplaces=0}) - - assert.same('\n' .. - '└─VLIST \n' .. - ' ╚═head:\n' .. - ' ├─GLUE width: -600bp\n' .. - ' └─HLIST \n' .. - ' ╚═head:\n' .. - ' ├─GLUE width: 100bp\n' .. - ' └─WHATSIT subtype: pdf_annot, width: 200bp, height: 100bp', - sanitize_node_str(_G.flare_stream:value())) -end) - - -test('Page:copyAnnots()', - function() - local d = Doc:new() - local p = Page:new(d) - p:setGinKV('filename', 'pdf/circle-01.pdf') - p:setGinKV('page', 1) - p:openFile() - p:copyAnnots() - - local nt = require('nodetree') - nt.print(_G.flare_box, - {verbosity=0, color='no', unit='bp', decimalplaces=0}) - - assert.same('\n' .. - '└─VLIST \n' .. - ' ╚═head:\n' .. - ' ├─GLUE width: -600bp\n' .. - ' └─HLIST \n' .. - ' ╚═head:\n' .. - ' ├─GLUE width: 100bp\n' .. - ' └─WHATSIT subtype: pdf_annot, width: 200bp, height: 100bp', - sanitize_node_str(_G.flare_stream:value())) - -end) - -end) -- describe diff --git a/test/test-flare-annot.tex b/test/test-flare-annot.tex deleted file mode 100644 index 1810e2d..0000000 --- a/test/test-flare-annot.tex +++ /dev/null @@ -1,11 +0,0 @@ -\RequirePackage{luatexbase} - -\directlua{ - require('test-flare-annot') -} -\stop - -%%% Local Variables: -%%% mode: latex -%%% TeX-master: t -%%% End: diff --git a/test/test-flare-format-annot1.lua b/test/test-flare-format-annot1.lua index 0033d26..13a2239 100644 --- a/test/test-flare-format-annot1.lua +++ b/test/test-flare-format-annot1.lua @@ -10,7 +10,7 @@ -- version 2008 or later. -- -- This work has the LPPL maintenance status `maintained'. --- +-- -- The Current Maintainer of this work is Andreas MATTHIAS. -- @@ -37,20 +37,123 @@ stringio = require('pl.stringio') nt = require('nodetree') require('helper') -describe('Testing flare-format-annot.lua:', function() +describe('Testing flare-annot.lua:', function() + + +test('Page:getAnnots()', + function() + local d = Doc:new() + local p = Page:new(d) + p:setGinKV('filename', 'pdf/circle-01.pdf') + p:setGinKV('page', 1) + p:openFile() + local annots = p:getAnnots() + assert.same(1, #annots) + local pt, pv, pd = pdfe.getfromarray(annots, 1) + assert.same(10, pt) + assert.same('pdfe.reference', pdfe.type(pv)) + assert.same(6, pd) +end) + +end) -- describe + + +describe('Testing flare-annot.lua:', function() + + +before_each(function() + orig_node_write = _G.node.write + _G.flare_box = nil + _G.node.write = function(val) _G.flare_box = val end + + orig_io_write = _G.io.write + _G.flare_stream = stringio.create() + _G.io.write = function(val) _G.flare_stream:write(val) end +end) + +after_each(function() + _G.node.write = orig_node_write + _G.io.write = orig_io_write +end) + +local function sanitize_node_str(str) + str = str:gsub(' objnum: %d+,', '') + str = str:gsub(', data: .*', '') + return str +end + + +test('Page:insertAnnot()', + function() + local d = Doc:new() + local p = Page:new(d) + p:setGinKV('filename', 'pdf/circle-01.pdf') + p:setGinKV('page', 1) + p:openFile() + local annot = pdfe.getpage(p.pdf, 1).Annots[1] + p.annotId = 1 + p:insertAnnot(annot) + + local nt = require('nodetree') + nt.print(_G.flare_box, + {verbosity=0, color='no', unit='bp', decimalplaces=0}) + + assert.same('\n' .. + '└─VLIST \n' .. + ' ╚═head:\n' .. + ' ├─GLUE width: -600bp\n' .. + ' └─HLIST \n' .. + ' ╚═head:\n' .. + ' ├─GLUE width: 100bp\n' .. + ' └─WHATSIT subtype: pdf_annot, width: 200bp, height: 100bp', + sanitize_node_str(_G.flare_stream:value())) +end) + + +test('Page:copyAnnots()', + function() + local d = Doc:new() + local p = Page:new(d) + p:setGinKV('filename', 'pdf/circle-01.pdf') + p:setGinKV('page', 1) + p:openFile() + p:copyAnnots() + + local nt = require('nodetree') + nt.print(_G.flare_box, + {verbosity=0, color='no', unit='bp', decimalplaces=0}) + + assert.same('\n' .. + '└─VLIST \n' .. + ' ╚═head:\n' .. + ' ├─GLUE width: -600bp\n' .. + ' └─HLIST \n' .. + ' ╚═head:\n' .. + ' ├─GLUE width: 100bp\n' .. + ' └─WHATSIT subtype: pdf_annot, width: 200bp, height: 100bp', + sanitize_node_str(_G.flare_stream:value())) + +end) + +end) -- describe + + +describe('Testing flare-annot.lua:', function() + test('Page:formatAnnotation()', function() local d = Doc:new() local p = Page:new(d) stub(pkg, 'warning') - + p:formatAnnotation({Subtype = 'DoesNotExist'}) assert.stub(pkg.warning).was_called() -- TODO: further tests needed end) - + + test('Page:getAnnotFunc()', function() local d = Doc:new() @@ -63,7 +166,7 @@ test('Page:getAnnotFunc()', assert.same(nil, p:getAnnotFunc({Subtype = 'doesNotExist'})) end) - + test('Page:getAnnotCommonEntries()', function() local p = Page:new(Doc:new()) @@ -239,7 +342,7 @@ test('Page:appendTable()', p:appendTable(t1, t2) assert.same({}, t1) assert.same({}, t2) - + t1 = {} t2 = {a=1, b=2} p:appendTable(t1, t2)