You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

519 lines
18 KiB

1 month ago
  1. %!PS
  2. % Copyright (C) 2001-2023 Artifex Software, Inc.
  3. % All Rights Reserved.
  4. %
  5. % This software is provided AS-IS with no warranty, either express or
  6. % implied.
  7. %
  8. % This software is distributed under license and may not be copied,
  9. % modified or distributed except as expressly authorized under the terms
  10. % of the license contained in the file LICENSE in this distribution.
  11. %
  12. % Refer to licensing information at http://www.artifex.com or contact
  13. % Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
  14. % CA 94129, USA, for further information.
  15. %
  16. % ZUGFeRD.ps
  17. % This program will create an (unsigned) ZUGFeRD compliant PDF file.
  18. % In order to do so the user must provide certain information, or edit
  19. % this program.
  20. %
  21. % Required information is the path to the XML file containing the invoice
  22. % data, and the path to an ICC profile appropriate for the chosen
  23. % ColorConversionStrategy.
  24. %
  25. % -sZUGFeRDXMLFile defines a path to the XML invoice file.
  26. %
  27. % -sZUGFeRDProfile defines the path to the ICC profile.
  28. %
  29. % -sZUGFeRDVersion defines the version of the ZUGFeRD standard to be used.
  30. % Missing or invalid values would be silently replaced by the default ("2p1").
  31. %
  32. % -sZUGFeRDConformanceLevel defines the level of conformance.
  33. % Missing or invalid values would be silently replaced by the default ("BASIC").
  34. %
  35. % Note that the ZUGFeRD standard states:
  36. %
  37. % The content of the field fx:ConformanceLevel has to be picked from
  38. % the content of the element "GuidelineSpecifiedDocumentContextParameter"
  39. % (specification identifier BT-24) of the XML instance file.
  40. %
  41. % Optionally:
  42. % -sZUGFeRDDateTime can be used to set a string representing the modification
  43. % date of the XML invoice file. If this is ommitted a dummy value will be
  44. % used. It is up to the user to create a correctly formatted PDF date/time
  45. % string. See section 7.9.4 of the PDF 2.0 specification (ISO-32000-2:2017)
  46. % for details of the format.
  47. %
  48. % The user must additionally set -dPDFA=3 and -sColorConversionStrategy
  49. % on the Ghostscript command line, and set the permissions for Ghostscript
  50. % to read both these files. It is simplest to put the files in a directory
  51. % and then permit reading of the entire directory.
  52. %
  53. % Example command line :
  54. %
  55. % gs --permit-file-read=/usr/home/me/zugferd/ \
  56. % -sDEVICE=pdfwrite \
  57. % -dPDFA=3 \
  58. % -sColorConversionStrategy=RGB \
  59. % -sZUGFeRDXMLFile=/usr/home/me/zugferd/invoice.xml \
  60. % -sZUGFeRDProfile=/usr/home/me/zugferd/rgb.icc \
  61. % -sZUGFeRDVersion=2p1 \
  62. % -sZUGFeRDConformanceLevel=BASIC \
  63. % -o /usr/home/me/zugferd/zugferd.pdf \
  64. % /usr/home/me/zugferd/zugferd.ps \
  65. % /usr/home/me/zugferd/original.pdf
  66. %
  67. % Much of this program results from a Ghostscript bug report, the thread
  68. % can be found at
  69. % https://bugs.ghostscript.com/show_bug.cgi?id=696472
  70. % Portions of the code below were supplied by Reinhard Nissl and
  71. % I'm indebted to him for his efforts in helping me create a solution for
  72. % this problem as well as for the code he supplied, particularly for the
  73. % SimpleUTF16BE routine.
  74. %
  75. % The program was further refined and expanded by Adrian Devries in :
  76. % https://bugs.ghostscript.com/show_bug.cgi?id=703862
  77. %
  78. % And refined again following feedback from Thorsten Engel in:
  79. % https://bugs.ghostscript.com/show_bug.cgi?id=707694
  80. %
  81. % It should not be necessary to modify this program, the comments in the
  82. % code are there purely for information, but there is one area which
  83. % might reasonably be altered. The section with the --8<-- lines could be
  84. % replaced with a simpler /N 3 or /N 4 if you always intend to produce
  85. % the same kind of files; RGB or CMYK.
  86. %
  87. % Remaining tasks have been marked with "TODO".
  88. % istring SimpleUTF16BE ostring
  89. /SimpleUTF16BE
  90. {
  91. dup length
  92. 1 add
  93. 2 mul
  94. string
  95. % istring ostring
  96. dup 0 16#FE put
  97. dup 1 16#FF put
  98. 2
  99. 3 -1 roll
  100. % ostring index istring
  101. {
  102. % ostring index ichar
  103. 3 1 roll
  104. % ichar ostring index
  105. 2 copy 16#00 put
  106. 1 add
  107. 2 copy
  108. 5 -1 roll
  109. % ostring index ostring index ichar
  110. put
  111. 1 add
  112. % ostring index
  113. }
  114. forall
  115. % ostring index
  116. pop
  117. }
  118. bind def
  119. % Cf. https://en.wikibooks.org/wiki/PostScript_FAQ#How_to_concatenate_strings%3F
  120. /concatstringarray { % [(a) (b) ... (z)] --> (ab...z)
  121. 0 1 index {
  122. length add
  123. } forall
  124. string
  125. 0 3 2 roll {
  126. 3 copy putinterval
  127. length add
  128. } forall
  129. pop
  130. } bind def
  131. /ZUGFeRDVersion where {
  132. pop % Discard the dictionary
  133. ZUGFeRDVersion (rc) ne {
  134. ZUGFeRDVersion (1p0) ne {
  135. ZUGFeRDVersion (2p0) ne {
  136. ZUGFeRDVersion (2p1) ne {
  137. /ZUGFeRDVersion (2p1) def
  138. } if
  139. } if
  140. } if
  141. } if
  142. }{
  143. /ZUGFeRDVersion (2p1) def
  144. } ifelse
  145. /ZUGFeRDConformanceLevel where {
  146. pop % Discard the dictionary
  147. ZUGFeRDVersion (rc) eq
  148. ZUGFeRDVersion (1p0) eq or {
  149. ZUGFeRDConformanceLevel (BASIC) ne {
  150. ZUGFeRDConformanceLevel (COMFORT) ne {
  151. ZUGFeRDConformanceLevel (EXTENDED) ne {
  152. /ZUGFeRDConformanceLevel (BASIC) def
  153. } if
  154. } if
  155. } if
  156. } if
  157. ZUGFeRDVersion (2p0) eq
  158. ZUGFeRDVersion (2p1) eq or {
  159. ZUGFeRDConformanceLevel (MINIMUM) ne {
  160. ZUGFeRDConformanceLevel (BASIC WL) ne {
  161. ZUGFeRDConformanceLevel (BASIC) ne {
  162. ZUGFeRDConformanceLevel (EN 16931) ne {
  163. ZUGFeRDConformanceLevel (EXTENDED) ne {
  164. ZUGFeRDConformanceLevel (XRECHNUNG) ne {
  165. /ZUGFeRDConformanceLevel (BASIC) def
  166. } if
  167. } if
  168. } if
  169. } if
  170. } if
  171. } if
  172. } if
  173. }{
  174. /ZUGFeRDConformanceLevel (BASIC) def
  175. } ifelse
  176. % ZUGFeRDSchema
  177. /ZUGFeRDSchema () def
  178. ZUGFeRDVersion (rc) eq
  179. ZUGFeRDVersion (1p0) eq or
  180. ZUGFeRDVersion (2p0) eq or {
  181. /ZUGFeRDSchema (ZUGFeRD PDFA Extension Schema) def
  182. } if
  183. ZUGFeRDVersion (2p1) eq {
  184. /ZUGFeRDSchema (Factur-X PDFA Extension Schema) def
  185. } if
  186. % ZUGFeRDNamespaceURI
  187. /ZUGFeRDNamespaceURI () def
  188. ZUGFeRDVersion (rc) eq {
  189. /ZUGFeRDNamespaceURI (urn:ferd:pdfa:invoice:rc#) def
  190. } if
  191. ZUGFeRDVersion (1p0) eq {
  192. /ZUGFeRDNamespaceURI (urn:ferd:pdfa:CrossIndustryDocument:invoice:1p0#) def
  193. } if
  194. ZUGFeRDVersion (2p0) eq {
  195. /ZUGFeRDNamespaceURI (urn:zugferd:pdfa:CrossIndustryDocument:invoice:2p0#) def
  196. } if
  197. ZUGFeRDVersion (2p1) eq {
  198. /ZUGFeRDNamespaceURI (urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0#) def
  199. } if
  200. % ZUGFeRDPrefix
  201. /ZUGFeRDPrefix () def
  202. ZUGFeRDVersion (rc) eq
  203. ZUGFeRDVersion (1p0) eq or
  204. ZUGFeRDVersion (2p0) eq or {
  205. /ZUGFeRDPrefix (zf) def
  206. } if
  207. ZUGFeRDVersion (2p1) eq {
  208. /ZUGFeRDPrefix (fx) def
  209. } if
  210. % ZUGFeRDVersionDescription
  211. /ZUGFeRDVersionDescription () def
  212. ZUGFeRDVersion (rc) eq
  213. ZUGFeRDVersion (1p0) eq or
  214. ZUGFeRDVersion (2p0) eq or {
  215. /ZUGFeRDVersionDescription (The actual version of the ZUGFeRD XML schema) def
  216. } if
  217. ZUGFeRDVersion (2p1) eq {
  218. /ZUGFeRDVersionDescription (The actual version of the Factur-X XML schema) def
  219. } if
  220. % ZUGFeRDConformanceLevelDescription
  221. /ZUGFeRDConformanceLevelDescription () def
  222. ZUGFeRDVersion (rc) eq
  223. ZUGFeRDVersion (1p0) eq or
  224. ZUGFeRDVersion (2p0) eq or {
  225. /ZUGFeRDConformanceLevelDescription (The conformance level of the embedded ZUGFeRD data) def
  226. } if
  227. ZUGFeRDVersion (2p1) eq {
  228. /ZUGFeRDConformanceLevelDescription (The conformance level of the embedded Factur-X data) def
  229. } if
  230. % ZUGFeRDDocumentFileName
  231. /ZUGFeRDDocumentFileName () def
  232. ZUGFeRDVersion (rc) eq {
  233. /ZUGFeRDDocumentFileName (ZUGFeRD-invoice.xml) def
  234. } if
  235. ZUGFeRDVersion (1p0) eq {
  236. /ZUGFeRDDocumentFileName (ZUGFeRD-invoice.xml) def
  237. } if
  238. ZUGFeRDVersion (2p0) eq {
  239. ZUGFeRDConformanceLevel (XRECHNUNG) ne {
  240. /ZUGFeRDDocumentFileName (zugferd-invoice.xml) def
  241. }{
  242. /ZUGFeRDDocumentFileName (xrechnung.xml) def
  243. } ifelse
  244. } if
  245. ZUGFeRDVersion (2p1) eq {
  246. ZUGFeRDConformanceLevel (XRECHNUNG) ne {
  247. /ZUGFeRDDocumentFileName (factur-x.xml) def
  248. }{
  249. /ZUGFeRDDocumentFileName (xrechnung.xml) def
  250. } ifelse
  251. } if
  252. % ZUGFeRDVersionData
  253. /ZUGFeRDVersionData () def
  254. ZUGFeRDVersion (rc) eq {
  255. /ZUGFeRDVersionData (RC) def
  256. } if
  257. ZUGFeRDVersion (1p0) eq {
  258. /ZUGFeRDVersionData (1.0) def
  259. } if
  260. ZUGFeRDVersion (2p0) eq {
  261. ZUGFeRDConformanceLevel (XRECHNUNG) ne {
  262. /ZUGFeRDVersionData (2p0) def
  263. }{
  264. /ZUGFeRDVersionData (1p2) def
  265. } ifelse
  266. } if
  267. ZUGFeRDVersion (2p1) eq {
  268. ZUGFeRDConformanceLevel (XRECHNUNG) ne {
  269. /ZUGFeRDVersionData (1.0) def
  270. }{
  271. /ZUGFeRDVersionData (1p2) def
  272. } ifelse
  273. } if
  274. /ZUGFeRDMetadata [
  275. (
  276. <rdf:Description)
  277. ( xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/")
  278. ( xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#")
  279. ( xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#")
  280. ( rdf:about="">
  281. <pdfaExtension:schemas>
  282. <rdf:Bag>
  283. <rdf:li rdf:parseType="Resource">
  284. <pdfaSchema:schema>)ZUGFeRDSchema(</pdfaSchema:schema>
  285. <pdfaSchema:namespaceURI>)ZUGFeRDNamespaceURI(</pdfaSchema:namespaceURI>
  286. <pdfaSchema:prefix>)ZUGFeRDPrefix(</pdfaSchema:prefix>
  287. <pdfaSchema:property>
  288. <rdf:Seq>
  289. <rdf:li rdf:parseType="Resource">
  290. <pdfaProperty:name>DocumentFileName</pdfaProperty:name>
  291. <pdfaProperty:valueType>Text</pdfaProperty:valueType>
  292. <pdfaProperty:category>external</pdfaProperty:category>
  293. <pdfaProperty:description>Name of the embedded XML invoice file</pdfaProperty:description>
  294. </rdf:li>
  295. <rdf:li rdf:parseType="Resource">
  296. <pdfaProperty:name>DocumentType</pdfaProperty:name>
  297. <pdfaProperty:valueType>Text</pdfaProperty:valueType>
  298. <pdfaProperty:category>external</pdfaProperty:category>
  299. <pdfaProperty:description>INVOICE</pdfaProperty:description>
  300. </rdf:li>
  301. <rdf:li rdf:parseType="Resource">
  302. <pdfaProperty:name>Version</pdfaProperty:name>
  303. <pdfaProperty:valueType>Text</pdfaProperty:valueType>
  304. <pdfaProperty:category>external</pdfaProperty:category>
  305. <pdfaProperty:description>)ZUGFeRDVersionDescription(</pdfaProperty:description>
  306. </rdf:li>
  307. <rdf:li rdf:parseType="Resource">
  308. <pdfaProperty:name>ConformanceLevel</pdfaProperty:name>
  309. <pdfaProperty:valueType>Text</pdfaProperty:valueType>
  310. <pdfaProperty:category>external</pdfaProperty:category>
  311. <pdfaProperty:description>)ZUGFeRDConformanceLevelDescription(</pdfaProperty:description>
  312. </rdf:li>
  313. </rdf:Seq>
  314. </pdfaSchema:property>
  315. </rdf:li>
  316. </rdf:Bag>
  317. </pdfaExtension:schemas>
  318. </rdf:Description>
  319. <rdf:Description xmlns:)ZUGFeRDPrefix(=")ZUGFeRDNamespaceURI(" rdf:about="">
  320. <)ZUGFeRDPrefix(:ConformanceLevel>)ZUGFeRDConformanceLevel(</)ZUGFeRDPrefix(:ConformanceLevel>
  321. <)ZUGFeRDPrefix(:DocumentFileName>)ZUGFeRDDocumentFileName(</)ZUGFeRDPrefix(:DocumentFileName>
  322. <)ZUGFeRDPrefix(:DocumentType>INVOICE</)ZUGFeRDPrefix(:DocumentType>
  323. <)ZUGFeRDPrefix(:Version>)ZUGFeRDVersionData(</)ZUGFeRDPrefix(:Version>
  324. </rdf:Description>
  325. )
  326. ] concatstringarray def
  327. /Usage {
  328. (example usage: \n) print
  329. ( gs --permit-file-read=/usr/home/me/zugferd/ \\\n) print
  330. ( -sDEVICE=pdfwrite \\\n) print
  331. ( -dPDFA=3 \\\n) print
  332. ( -sColorConversionStrategy=RGB \\\n) print
  333. ( -sZUGFeRDXMLFile=/usr/home/me/zugferd/invoice.xml \\\n) print
  334. ( -sZUGFeRDProfile=/usr/home/me/zugferd\rgb.icc \\\n) print
  335. ( -sZUGFeRDVersion=2p1 \\\n) print
  336. ( -sZUGFeRDConformanceLevel=BASIC \\\n) print
  337. ( -o /usr/home/me/zugferd/zugferd.pdf \\\n) print
  338. ( /usr/home/me/zugferd/zugferd.ps \\\n) print
  339. ( /usr/home/me/zugferd/original.pdf \n) print
  340. flush
  341. } def
  342. % First check that the user has defined the XML invoice file on the command line
  343. %
  344. /ZUGFeRDXMLFile where {
  345. pop % Discard the dictionary
  346. %
  347. % Now check that the ICC Profile is defined
  348. %
  349. /ZUGFeRDProfile where {
  350. pop % Discard the dictionary
  351. % Step 1, add the required PDF/A boilerplate.
  352. % This is mostly copied from lib/pdfa_def.ps
  353. % Create a PDF stream object to hold the ICC profile.
  354. [ /_objdef {icc_PDFA} /type /stream /OBJ pdfmark
  355. % Add the required entries to the stream dictionary (/N only)
  356. [ {icc_PDFA}
  357. <<
  358. %% This code attempts to set the /N (number of components) key for the ICC colour space.
  359. %% To do this it checks the ColorConversionStrategy or the device ProcessColorModel if
  360. %% ColorConversionStrategy is not set.
  361. %% This is not 100% reliable. A better solution is for the user to edit this and replace
  362. %% the code between the ---8<--- lines with a simple declaration like:
  363. %% /N 3
  364. %% where the value of N is the number of components from the profile defined in ZUGFeRDProfile.
  365. %%
  366. %% ----------8<--------------8<-------------8<--------------8<----------
  367. systemdict /ColorConversionStrategy known {
  368. systemdict /ColorConversionStrategy get cvn dup /Gray eq {
  369. pop /N 1 false
  370. }{
  371. dup /RGB eq {
  372. pop /N 3 false
  373. }{
  374. /CMYK eq {
  375. /N 4 false
  376. }{
  377. (ColorConversionStrategy not a device space, falling back to ProcessColorModel, output may not be valid PDF/A.)=
  378. true
  379. } ifelse
  380. } ifelse
  381. } ifelse
  382. } {
  383. (ColorConversionStrategy not set, falling back to ProcessColorModel, output may not be valid PDF/A.)=
  384. true
  385. } ifelse
  386. {
  387. currentpagedevice /ProcessColorModel get
  388. dup /DeviceGray eq {
  389. pop /N 1
  390. }{
  391. dup /DeviceRGB eq {
  392. pop /N 3
  393. }{
  394. dup /DeviceCMYK eq {
  395. pop /N 4
  396. } {
  397. (ProcessColorModel not a device space.)=
  398. /ProcessColorModel cvx /rangecheck signalerror
  399. } ifelse
  400. } ifelse
  401. } ifelse
  402. } if
  403. %% ----------8<--------------8<-------------8<--------------8<----------
  404. >> /PUT pdfmark
  405. % Now read the ICC profile from the file into the stream
  406. [ {icc_PDFA} ZUGFeRDProfile (r) file /PUT pdfmark
  407. % Define the output intent dictionary :
  408. [/_objdef {OutputIntent_PDFA} /type /dict /OBJ pdfmark
  409. % Add the required keys to the dictionary
  410. [{OutputIntent_PDFA} <<
  411. /Type /OutputIntent
  412. /S /GTS_PDFA1 % Required for PDF/A.
  413. /DestOutputProfile {icc_PDFA} % The actual profile.
  414. /OutputConditionIdentifier (Custom) % TODO: A better solution is a
  415. % a string from the ICC
  416. % Registry, but Custom
  417. % is always valid.
  418. >> /PUT pdfmark
  419. % And now add the OutputIntent to the Catalog dictionary
  420. [ {Catalog} << /OutputIntents [ {OutputIntent_PDFA} ]>> /PUT pdfmark
  421. % Step 2, define the XML file and read it into the PDF
  422. % First we define the PDF stream to contain the XML invoice
  423. [ /_objdef {InvoiceStream} /type /stream /OBJ pdfmark
  424. % Fill in the dictionary elements we need. We believe the
  425. % ModDate is not useful so it's just set to a valid value.
  426. [ {InvoiceStream} <<
  427. /Type /EmbeddedFile
  428. /Subtype (text/xml) cvn
  429. /Params <<
  430. /ModDate systemdict /ZUGFeRDDateTime known
  431. {ZUGFeRDDateTime}
  432. {(D:20130121081433+01'00')}
  433. ifelse
  434. /Size ZUGFeRDXMLFile status
  435. {pop pop exch pop}
  436. {(Failed to get file status!\n)print /status load /ioerror signalerror}
  437. ifelse
  438. >>
  439. >> /PUT pdfmark
  440. % Now read the data from the file and store it in the stream
  441. [ {InvoiceStream} ZUGFeRDXMLFile (r) file /PUT pdfmark
  442. % and close the stream
  443. [ {InvoiceStream} /CLOSE pdfmark
  444. % Step 3 create the File Specification dictionary for the embedded file
  445. % Create the dictionary
  446. [ /_objdef {FSDict} /type /dict /OBJ pdfmark
  447. % Fill in the required dictionary elements
  448. [ {FSDict} <<
  449. /Type /Filespec
  450. /F ZUGFeRDDocumentFileName
  451. /UF ZUGFeRDDocumentFileName SimpleUTF16BE
  452. /Desc (ZUGFeRD electronic invoice)
  453. /AFRelationship /Alternative
  454. /EF <<
  455. /F {InvoiceStream}
  456. /UF {InvoiceStream}
  457. >>
  458. >>
  459. /PUT pdfmark
  460. % Step 4 Create the Associated Files dictionary to hold the FS dict
  461. % Create the dictionary
  462. [ /_objdef {AFArray} /type /array /OBJ pdfmark
  463. % Put (append) the FS dictionary into the Associated Files array
  464. [ {AFArray} {FSDict} /APPEND pdfmark
  465. % Step 5 Add an entry in the Catalog dictionary containing the AF array
  466. [ {Catalog} << /AF {AFArray} >> /PUT pdfmark
  467. % Step 6 use the EMBED pdfmark to add the XML file and FS dictionary to the PDF name tree
  468. [ /Name ZUGFeRDDocumentFileName /FS {FSDict} /EMBED pdfmark
  469. % Step 7 Add the extra ZUGFeRD XML data to the Metadata
  470. [ /XML ZUGFeRDMetadata /Ext_Metadata pdfmark
  471. }
  472. {
  473. % No ICC Profile definition on the command line;
  474. % chide the user and give them an example
  475. (\nERROR - ZUGFeRDProfile has not been supplied, you must supply an ICC profile) print
  476. (\n Producing a potentially INVALID PDF/A file. \n) print
  477. Usage
  478. } ifelse
  479. }
  480. {
  481. % No XML invoice definition on the command line;
  482. % chide the user and give them an example
  483. (\nERROR - ZUGFeRDXMLFile has not been supplied, you must supply a XML invoice file) print
  484. (\n Producing a PDF/A file, NOT a ZUGFeRD file. \n) print
  485. Usage
  486. } ifelse
  487. % That's all the ZUGFeRD and PDF/A-3 setup completed,
  488. % all that remains now is to run the input file
  489. %%EOF